Go 训练营第 4 周总结
本周主要介绍了 go 工程化的设计理念。
工程目录结构
先上个“标准”目录的 demo:
介绍几个重要的目录层级及其设计初衷
/cmd
项目主干,即程序入口,如果是多个子服务共用一个项目,则这里有多个子目录,每个子目录编译出一个可执行文件,文件名称与子目录名称相同。
关于子服务(service),主要包含:
admin:内部管理;
interface:对外的 BFF 服务,如 gRPC 接口;
service:对内的微服务接口,如 gRPC 接口;
job:流式处理服务,如监控消息队列的 consumer;
task:定时任务;
/internal
私有应用程序和库代码。由 go 编译器实现,可以隔离私有代码,比如:.../a/b/c/internal/d/e/ 可以由.../a/b/c 加载,但不能被.../a/b/g 加载。
如果该项目有多个子服务,且需要保留各子服务的代码私有部分,可以在 internal 下添加多个子目录,每个子目录存放对应的子服务,且子目录名称与/cmd 的子目录对应。
介绍 internal 下的各子目录,项目的主体逻辑:
biz: 业务逻辑的组装层,包含数据模型的定义,类似 DDD 的 domain 层,data 类似 DDD 的 repo,repo 接口在这里定义,使用依赖倒置的原则;
data: 业务数据访问,包含 cache、db 等封装,实现了 biz 的 repo 接口;
server: 放置 HTTP/gRPC 的路由代码,以及 DTO 转换的代码;
service: 实现了 api 定义的服务层,类似 DDD 的 application 层,处理 DTO 到 biz 领域实体的转换(DTO -> DO,DTO 一般在 RPC 或 http 接口上定义),同时协同各类 biz 交互,但是不应处理复杂逻辑;
这里主要是运用了依赖倒置的设计原则,使用的是依赖注入的方法。
/pkg
公用的库代码。可用于跨多个项目使用,一般放在 interval/pkg。
pkg 里的组件目录结构可参考官方标准库按功能拆分。
如果根目录包含许多非 GO 组件,可以考虑将 pkg 放在根目录,并用来放这些组件。
可参考https://travisjeffery.com/b/2019/11/i-ll-take-pkg-over-internal/
/api
对外的接口
/configs
项目配置
/test
测试相关的数据、脚本等
服务的依赖管理
可以使用 wire 来托管服务间的依赖(依赖注入的方式),避免手撸代码导致的错误,也为了方便单元测试和复用初始化的对象。
API 设计
gRPC
最核心的是 IDL,基于文件定义服务,服务接口的声明(或文档)永远不会和实现产生偏差;
轻量级、高性能;
API 规范方法
建立一个专用的仓库,用于管理、检索、规范所有的 API,也可用于生成服务树。
兼容性
一般只添加,修改或删除需要考虑向后兼容问题。
命名
请求 url: <package_name>.<version>.<service_name>/method
包名:<package_name>.<version>
错误处理
错误信息需要能够定位到是哪个服务的异常;
错误在服务间传递应该是翻译错误,不应该透传;
避免整个系统级的全局错误码;
用 PB 文件定义的 Isxxx 判断错误类型,统一客户端、服务端的判断“姿势”;
接口参数
统一更新多字段信息的接口,可用 protobuf 的 FiledMask 来指定更新某个字段。
protobuf 中可以使用 wrapper 包装实现可选和必选参数?
配置
简单的策略可以使用动态配置
不推荐使用动态配置第三方依赖,支持热更新需要复杂的兼容;
function options
TODO,不懂
测试
方法:mock/fake stub
基础库:大量单元测试
中间件:chaos 测试——模拟各种故障
微服务:接口测试
go 官方更新 subTest+gomock 做单元测试
go subTest 可以解决测试前后的资源初始化与释放;
data 层:docker-compose 创建资源依赖;
biz: gomock 模拟 interface 实现;
建议只测试 api(端到端测试),使用 yapi;
评论