这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天。
项目实践
1,需求设计
需求背景:
以掘金社区话题页面为例,页面的功能包括话题详情、回帖列表、支持回帖、点赞、和回帖回复。我们以此为需求模型,开发一个类似该页面的服务端小功能。
需求描述
社区话题页面包括:
- 展示话题(标题,文字描述)和回帖列表
- 暂不考虑前端页面实现,仅实现一个本地web服务
- 话题和回帖数据用文件存储(暂不使用数据库)
需求用例
用户浏览消费涉及页面的展示,包括话题内容和回帖的列表。下图中可以抽出两个实体,话题(Topic)和帖子(PostList)。
他们可以通过ER(Entity Relationship Diagram)图来描述现实世界的概念模型。有了模型实体和属性之间的联系,对后续开发提供了清晰思路。
有了实体模型,下一步是代码结构设计,这里采用典型的分层结构设计。
整体分为三层:
- 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图定义结构体:
但是如何实现查询呢(QueryTopicById, QueryPostsByParentId)?
Repository-index
首先,查询可以使用全扫描遍历的方式,但是效率较低。因此,引出索引的概念,这里使用map实现内存索引,在服务对外暴露前,利用文件元数据初始化全局内存索引,这样就可以实现O(1)的时间复杂度查找操作。
代码实现(初始化话题数据索引):首先打开文件 -> 基于file初始化scanner -> 通过迭代器方式遍历数据行 -> 转化为结构体存至内存map
Repository-查询
有了内存索引,可以直接使用key查询获得map中的value,这里的sync.once适用于高并发的场景下只执行一次的场景,Once的实现模式是单例模式,减少了存储的浪费。
Service逻辑层
定义完成Repository数据层,接下来实现Service层,包括话题Topic和帖子PostList。流程为参数校验->准备数据->组装实体。
具体Service流程编排,通过err控制流程退出,err为nil时表示正常返回页面信息:
函数prepareInfo中获取话题和回帖信息都依赖topicId,所以两者可以并行执行,提升执行效率。后续开发中一定要思考流程是否可以并行,通过利用CPU来降低接口耗时,若串行实现会浪费CPU资源。
Controller视图层
定义完成Service层,接下来是Controller层。我们需要定义一个View对象,通过code msg打包业务状态信息,用data承载业务实体信息。
Router
开发最后一步是Gin web服务的引擎配置,包括初始化数据索引、初始化引擎配置、构建路由、启动服务。
3,测试运行
代码开发完成后,进行完备的单元测试,快速定位bug并修复后,执行go run本地启动web服务,通过curl命令请求服务,通过curl命令请求服务暴露的接口。
总结
以上是对社区话题页面的整个实现流程,从项目拆解->代码设计落地->测试运行。后续项目可以通过将大需求拆分成不同小需求来逐个击破,并最后做好充分测试。