1. 工程项目
1.1 项目结构
可以参考:https://github.com/golang-standards/project-layout/blob/master/README_zh.md
/cmd
本项目的主干,cmd应用目录负责程序的:启动、关闭、配置初始化等。每个应用程序的目录名应该与你想要的可执行文件的名称相匹配(例如,/cmd/myapp)。
不要在这个目录中放置太多代码。如果你认为代码可以导入并在其他项目中使用,那么它应该位于 /pkg 目录中。如果代码不是可重用的,或者你不希望其他人重用它,请将该代码放到 /internal 目录中。
/pkg
希望被别人重用,放在 pkg。
外部应用程序可以使用的库代码(如,
/pkg/mypubliclib
),pkg目录内,可以参考go标准库的组织方式,按照功能分类。/internal
私有应用程序和库代码,这是你不希望其他人在其应用程序或库中导入代码。
1.2 kit 基础库
每个公司都应当为不同的微服务建立一个统一的 kit 工具包项目(基础库/框架)和app项目。
基础库kit为独立项目,公司级建议只有一个,按照功能目录来拆分会带来不少的管理工作,因此建议合并整合。
kit项目必须具备的特点:
- 统一
- 标准库方式布局
- 高度抽象
- 支持插件
1.3 服务项目结构
/api
API协议定义目录,xxapi.proto protobuf文件,以及生成的go 文件。我们通常把api文档直接proto文件中描述。
/config
配置文件模板或默认配置。
/test
额外的外部测试应用程序和测试数据。你可以随时根据需求构造 /test目录。
对于较大的顶目,有一个数据子目录是有意义的。例如, 你可以使用test/data或test/testdata(如果你需要忽略目录中的内
容)。
你不应该拥有的目录
/src
有些 Go 项目确实有一个
src
文件夹,但这通常发生在开发人员有 Java 背景,在那里它是一种常见的模式。如果可以的话,尽量不要采用这种 Java 模式。你真的不希望你的 Go 代码或 Go 项目看起来像 Java:-)
1.4 服务应用项目
一个gitlab的project里可以放置多个微服务的 app(类似monorepo)。也可以按照gitlab的 group里建立多个project,每个project对应一个 app。
微服务中的app服务类型分为几类:interface、 service、job、admin、task。
- interface: 对外的 BFF 服务,接受来自用户的请求,比如暴露了 HTTP/gRPC 接口。
- service: 对内的微服务,仅接受来自内部其他服务或者网关的请求,比如暴露了 gRPC 接口只对内服务。
- admin:区别于 service,更多是面向运营测的服务,通常数据权限更高,隔离带来更好的代码级别安全。
- job: 流式任务处理的服务,上游一般依赖 message broker(kafka)。
- task: 定时任务,类似 cronjob,部署到 task 托管平台中(可以用k8s的cronjob)。
1.5 示例

- app 目录下有 api、cmd、configs、internal 目录,目录里一般还会放置 README、CHANGELOG、OWNERS。
- internal: 是为了避免有同业务下有人跨目录引用了内部的 biz、data、service 等内部 struct。
- 如果存在一个仓库多个应用,那么可以在 internal 里面进行分层,例如
/internal/app
,/internal/job
- biz: 业务逻辑的组装层,类似 DDD 的 domain 层,data 类似 DDD 的 repo,repo 接口在这里定义,使用依赖倒置的原则。
- data: 业务数据访问,包含 cache、db 等封装,实现了 biz 的 repo 接口。我们可能会把 data 与 dao 混淆在一起,data 偏重业务的含义,它所要做的是将领域对象重新拿出来,我们去掉了 DDD 的 infra 层。
- service: 实现了 api 定义的服务层,类似 DDD 的 application 层,处理 DTO 到 biz 领域实体的转换(DTO -> DO),同时协同各类 biz 交互,但是不应处理复杂逻辑。
- 如果存在一个仓库多个应用,那么可以在 internal 里面进行分层,例如
2. 模型设计
2.1 数据模型
DTO(Data Transfer Object)
数据传输对象。这个概念来源于J2EE的设计模式。但在这里,泛指用于展示层AP1层与服务层业务逻辑层)之间的数据传输对象。
就是数据库映射的类型和展示的类型,要进行一些裁剪。
DO(Domain Obiect)
领域对象,就是从现实世界中抽象出来的有形或无形的业务实体,第一版缺乏DTO->DO的对象转换,DO理解成以前的model。
PO(Persistent Object)
持久化对象,它跟持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系,如果持久层是关系型数据库,那么数据表中的每个字段(或若干个)就对应 PO 的一个(或若干个)属性。

2.2 Biz 领域模型

3. API设计
3.1 API 项目
https://github.com/envoyproxy/data-plane-api
为了统一检索和规范API,我们内部建立了一个统一的bapis仓库,整合所有对内对外API。
- API仓库,方便跨部门协作。
- 版本管理,基于gt控制。
- 规范化检查,API lint。
- API design review,变更diff。
- 权限管理,目录OWNERS。
3.2 API 兼容性设计
- 向下兼容的变更
- 新增接口
- 新增参数字段
- 新增返回字段
- 向下不兼容的变更(破坏性变更)
- 删除或重命名服务,字段,方法或枚举值
- 如果只有很少的 api 变动可以创建一个 XXXV2 的方法
- 如果变动的 api 比较多,可以直接新启一个 v2 的包
- 修改字段的类型
- 修改现有请求的可见行为
- 给资源消息添加 读取/写入 字段
3.3 API 命名规范
- 包名
产品名 | product |
---|---|
应用名 | app |
版本号 | v1 |
包名 | product.app.v1 |
目录结构 | api/product/app/v1/xx.proto |
3.4 API Protobuf 字段
oneof 最多同时设置其中一个字段
如果你有一条包含多个字段的消息,并且最多同时设置其中一个字段,那么你可以通过使用
oneof
来实现并节省内存。1
2
3
4
5
6
7
8// 通知读者的消息
message NoticeReaderRequest{
string msg = 1;
oneof notice_way{
string email = 2;
string phone = 3;
}
}WrapValue 默认值问题
gRPC默认使用Protobuf v3格式,因为去除了 required和optional关键字,默认全部都是 optional字段。如果没有赋值的字段,默认会基础类型字段的默认值,比如0或者””。(v3.15.0又加回来了)
可以通过包裹一下解决,这样就有指针类型了。
1 | message DoubleValue { |
- FieldMask 只更新个别字段
1 | message UpdateBookRequest { |
3.5 API Errors
- 需要细节错误的再定义小错误。一般直接返回一个大错误。
- 错误传播:隐藏机密信息,感兴趣的可以把错误转换成自己的错误码,翻译了错误码。
- 错误码的唯一性,一般和服务绑定的。全局的错误码比较松散。
1 | { |
4. 配置管理
4.1 分类
- 环境配置
- 环境配置,应该是应用部署时就已经确定好的信息,这些信息不应该写在我们的配置文件或者是放到配置中心,而是应该由我们的部署平台,例如 K8s 直接在容器启动时候就注入好。
- region: 区域信息
- env: 环境信息,例如 prod, test
- zone: 可用区
- host: 机器名
- appid: 应用 id
- color: 流量染色信息,用来做流量分发的
- 静态配置【固定不reload】
- 资源需要初始化的配置信息,比如 http/gRPC server、redis、mysql 等。
- 这类资源在线变更配置的风险非常大,尽量不要在线动态变更,很可能会导致业务出现不可预期的事故。
- 变更静态配置和发布 bianry app 没有区别,应该走一次迭代发布的流程。
- 动态配置
- 应用程序可能需要一些在线的开关,来控制业务的一些简单策略,会频繁的调整和使用,我们把这类是基础类型(int, bool)等配置,用于可以动态变更业务流的收归一起。可以结合:https://pkg.go.dev/expvar 使用。
- 全局配置
- 我们依赖的各类组件、中间件都有大量的默认配置或者指定配置,在各个项目里大量拷贝复制,容易出现意外,所以我们使用全局配置模板来定制化常用的组件,然后再特化的应用里进行局部替换。
4.2 函数参数配置
- 函数可选方案
1 | type DialOption func(*dialOptions) |
4.3 最佳实践
- 避免复杂
- 多样的配置
- 简单化努力
- 以基础设施(越多越好) -> 面向用户进行转变
- 配置的必选项和可选项
- 配置的防御编程(直接panic)
- 权限和变更跟踪
- 配置的版本和应用对齐,这个很多都没做到,经常应用回滚了配置没回滚,就出事故了
- 安全的配置变更:逐步部署、回滚更改、自动回滚