go-clean-template源码分析
April 3, 2022
简介
go-clean-template是一个golang的微服务模板,按照Robert Martin《代码整洁之道》的各原则来实现。业务方可以使用该模板来扩展构建自己的微服务。
提供的能力项包括:
- 按照DIP、ISP等原则来组织框架层、业务逻辑层以及数据层等代码实现。
- 配置文件解析,包括端口号、日志、PG等RabbitMQ配置。
- 集成gin框架,提供了几个HTTP接口demo实现。
- 集成了RabbitMQ RPC,提供了1个接口的demo实现。
- 集成了日志、Swagger、K8s probe、Prometheus上报等能力。
本项目主要是作为模板来演示如何构建可扩展、易于维护的微服务,因此在实现上比较简单,业务方可以根据实际需求进一步改造扩充。
工程架构
启动方式
README中给出了docker的构建方式,也可以将相关代码copy出来之后自行go build构建。
注意服务依赖RabbitMQ、PostgreSQL,启动前需要搭建对应的容器或者实例。
目录结构
- cmd: cmd处理逻辑。
- config:配置解析
- docker-compose.yml: Image构建
- internal:内部服务的代码逻辑,与框架、pkg等实现上完全隔离。
- app: app的具体实现,包括配置解析、业务逻辑注入、http/RPC启动、signal响应以及退出逻辑
- controller:对外的API层实现,包括http、RPC接口的路由以及实现。
- entity:entity层,通常用于公共数据结构的声明。
- usecase:业务逻辑实现,采用了DIP原则,内容包括了接口声明、业务逻辑实现以及单侧等。
- pkg:独立的模块
- http server:http服务启动
- logger:日志实现,基于zerolog
- postgres:封装了golang postgre实现
- rabbitmq:封装rabbit RPC。
核心设计
代码整洁之道的不少原则在项目中都有体现
ISP:接口隔离原则
定义:任何层次的软件设计如果依赖了它并不需要的东西,就会带来意料之外的麻烦。
User case中Repo(缓存存取数据)和WebAPI(拉取外部数据),虽然都属于业务逻辑流程中的一部分,但是在实现上完全隔离,相互间无依赖关系。数据存储不依赖API的实现。
LSP:里氏替换原则
定义1:如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。定义2:所有引用基类的地方必须能透明地使用其子类的对象
pkg中的各个独立模块的实现就有LSP的体验,比如log的实现,log的内部实现上依赖了zerolog,但是zerolog没有扩散到代码的其他部分。理论上log内部实现可以被其他log lib替换,比如glog、zap等,而且上层无需感知。
DIP:依赖倒置
定义:源代码层次的依赖关系中就应该多引用抽象类型,而非具体实现。
user case的实现就是典型的DIP原则。
- 声明user case的interface。在代码的其他模块仅仅依赖interface,不依赖interface的具体实现。
Loading...
- app启动前将业务逻辑的具体实现注入到user case中,供其他业务逻辑使用。
Loading...
通过New构造函数,将依赖注入到业务逻辑的结构中. 这使得业务逻辑独立于外部接口。业务逻辑内部实现之间也也是通过Interface来交互,没有直接依赖相互的实现。这样实现后续在业务逻辑的实现变更时,比如更换WebAPI依赖的后台接口,或者将数据存储从Postgre迁移到Mysql时,对其他模块没有任何影响。
DIP的另外一大收益为单测,可以自动生成mock,以便进行单元测试。
Loading...
总结
- 代码整体上可以分为内网两层,外传主要包括路由、http/RCP API处理、以及PG、MQ等组件的封装。内层主要为业务逻辑的实现。
- 各层之间基本没有直接依赖关系,尽量是通过各种Interface进行通信。
其他实践
- 配置解析:使用了cleanenv,一个比较简单的实现。个人感觉可以使用vip等替换。
- 文档中推荐后续采用wire来管理依赖关系。没有实际研究过wire,初步感觉有点鸡肋。但是不确定在大型项目中是否有较大收益,后续可以关注。
- 业务逻辑的实现(usercase/)中,按应用领域了进行了分组。
Reference
See all postsSee all posts