GO项目实践 | 青训营笔记

167 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天。

项目实践

1,需求设计

需求背景:

以掘金社区话题页面为例,页面的功能包括话题详情、回帖列表、支持回帖、点赞、和回帖回复。我们以此为需求模型,开发一个类似该页面的服务端小功能。 image.png

需求描述

社区话题页面包括:

  • 展示话题(标题,文字描述)和回帖列表
  • 暂不考虑前端页面实现,仅实现一个本地web服务
  • 话题和回帖数据用文件存储(暂不使用数据库)

需求用例

用户浏览消费涉及页面的展示,包括话题内容和回帖的列表。下图中可以抽出两个实体,话题(Topic)和帖子(PostList)。

image.png

他们可以通过ER(Entity Relationship Diagram)图来描述现实世界的概念模型。有了模型实体和属性之间的联系,对后续开发提供了清晰思路。 image.png

有了实体模型,下一步是代码结构设计,这里采用典型的分层结构设计。 image.png

整体分为三层:

  • Repository数据层:数据层关联底层数据模型(model),封装外部数据的增删改查;我们的数据存储在本地文件,通过文件操作拉取话题,帖子数据;数据层面向逻辑层,对逻辑层透明,屏蔽下游数据差异,也就是不管下游是文件、微服务还是数据库等,都对逻辑层的接口模型是不变的。
  • Service逻辑层:处理核心业务逻辑,计算打包业务实体Entity,对应需求话题页面的话题和回帖列表,并传送给视图层。
  • Controller视图层:负责处理和外部的交互逻辑,以View视图形式返回给客户端,在我们需求中,只需封装JSON格式化的请求结果,api形式访问。

2,代码开发

组件工具

搭建Web服务器首先涉及的基础组件就是Gin,一款高性能的开源go web框架。此项目主要涉及路由分发,不会涉及其他复杂概念。引入web框架后,涉及到了go module的依赖管理。首先go mod初始化管理配置文件,然后go get下载Gin依赖。完成依赖配置,只需关注业务本身,从Repository数据层->Service逻辑层->Controller视图层,一步步实现。

Repository数据层

首先根据前面的ER图定义结构体:

image.png 但是如何实现查询呢(QueryTopicById, QueryPostsByParentId)?

Repository-index

首先,查询可以使用全扫描遍历的方式,但是效率较低。因此,引出索引的概念,这里使用map实现内存索引,在服务对外暴露前,利用文件元数据初始化全局内存索引,这样就可以实现O(1)的时间复杂度查找操作。

image.png

代码实现(初始化话题数据索引):首先打开文件 -> 基于file初始化scanner -> 通过迭代器方式遍历数据行 -> 转化为结构体存至内存map

image.png

Repository-查询

有了内存索引,可以直接使用key查询获得map中的value,这里的sync.once适用于高并发的场景下只执行一次的场景,Once的实现模式是单例模式,减少了存储的浪费。

image.png

Service逻辑层

定义完成Repository数据层,接下来实现Service层,包括话题Topic和帖子PostList。流程为参数校验->准备数据->组装实体。

image.png

具体Service流程编排,通过err控制流程退出,err为nil时表示正常返回页面信息:

image.png

函数prepareInfo中获取话题和回帖信息都依赖topicId,所以两者可以并行执行,提升执行效率。后续开发中一定要思考流程是否可以并行,通过利用CPU来降低接口耗时,若串行实现会浪费CPU资源。

image.png

Controller视图层

定义完成Service层,接下来是Controller层。我们需要定义一个View对象,通过code msg打包业务状态信息,用data承载业务实体信息。

image.png

Router

开发最后一步是Gin web服务的引擎配置,包括初始化数据索引、初始化引擎配置、构建路由、启动服务。

image.png

3,测试运行

代码开发完成后,进行完备的单元测试,快速定位bug并修复后,执行go run本地启动web服务,通过curl命令请求服务,通过curl命令请求服务暴露的接口。

image.png

总结

以上是对社区话题页面的整个实现流程,从项目拆解->代码设计落地->测试运行。后续项目可以通过将大需求拆分成不同小需求来逐个击破,并最后做好充分测试。