go 工程化实践
目录结构
/cmd
本项目的主干。
每个应用程序的目录名应该与你想要的可执行文件的名称相匹配(例如,/cmd/myapp)。
不要在这个目录中放置太多代码。如果你认为代码可以导入并在其他项目中使用,那么它应该位于 /pkg 目录中。如果代码不是可重用的,或者你不希望其他人重用它,请将该代码放到 /internal 目录中
/internal
私有应用程序和库代码。这是你不希望其他人在其应用程序或库中导入代码。请注意,这个布局模式是由 Go 编译器本身执行的。有关更多细节,请参阅 Go 1.4 release notes。注意,你并不局限于顶级 internal 目录。在项目树的任何级别上都可以有多个内部目录。
你可以选择向 internal 包中添加一些额外的结构,以分隔共享和非共享的内部代码。这不是必需的(特别是对于较小的项目),但是最好有有可视化的线索来显示预期的包的用途。你的实际应用程序代码可以放在 /internal/app 目录下(例如 /internal/app/myapp),这些应用程序共享的代码可以放在 /internal/pkg 目录下(例如 /internal/pkg/myprivlib)。
因为我们习惯把相关的服务,比如账号服务,内部有 rpc、job、admin 等,相关的服务整合一起后,需要区分 app。单一的服务,可以去掉 /internal/myapp。
/pkg
外部应用程序可以使用的库代码(例如 /pkg/mypubliclib)。其他项目会导入这些库,所以在这里放东西之前要三思:-)注意,internal 目录是确保私有包不可导入的更好方法,因为它是由 Go 强制执行的。/pkg 目录仍然是一种很好的方式,可以显式地表示该目录中的代码对于其他人来说是安全使用的好方法。
/pkg 目录内,可以参考 go 标准库的组织方式,按照功能分类。/internla/pkg 一般用于项目内的 跨多个应用的公共共享代码,但其作用域仅在单个项目工程内。
由 Travis Jeffery 撰写的 I’ll take pkg over internal 博客文章提供了 pkg 和 internal 目录的一个很好的概述,以及什么时候使用它们是有意义的。
当根目录包含大量非 Go 组件和目录时,这也是一种将 Go 代码分组到一个位置的方法,这使得运行各种 Go 工具变得更加容易组织
Kit Project Layout
每个公司都应当为不同的微服务建立一个统一的 kit 工具包项目(基础库/框架) 和 app 项目。
基础库 kit 为独立项目,公司级建议只有一个,按照功能目录来拆分会带来不少的管理工作,因此建议合并整合。
by Package Oriented Design
“To this end, the Kit project is not allowed to have a vendor folder. If any of packages are dependent on 3rd party packages, they must always build against the latest version of those dependences.”
kit 项目必须具备的特点:
统一
标准库方式布局
高度抽象
支持插件
/api
API 协议定义目录,xxapi.proto protobuf 文件,以及生成的 go 文件。我们通常把 api 文档直接在 proto 文件中描述。
/configs
配置文件模板或默认配置。
/test
额外的外部测试应用程序和测试数据。你可以随时根据需求构造 /test 目录。对于较大的项目,有一个数据子目录是有意义的。例如,你可以使用 /test/data 或 /test/testdata (如果你需要忽略目录中的内容)。请注意,Go 还会忽略以“.”或“_”开头的目录或文件,因此在如何命名测试数据目录方面有更大的灵活性。
不应该包含:/src
有些 Go 项目确实有一个 src 文件夹,但这通常发生在开发人员有 Java 背景,在那里它是一种常见的模式。不要将项目级别 src 目录与 Go 用于其工作空间的 src 目录。
Service Application Project
一个 gitlab 的 project 里可以放置多个微服务的 app(类似 monorepo)。也可以按照 gitlab 的 group 里建立多个 project,每个 project 对应一个 app。
多 app 的方式,app 目录内的每个微服务按照自己的全局唯一名称,比如 “account.service.vip” 来建立目录,如: account/vip/*。
和 app 平级的目录 pkg 存放业务有关的公共库(非基础框架库)。如果应用不希望导出这些目录,可以放置到 myapp/internal/pkg 中。
微服务中的 app 服务类型分为 4 类:interface、service、job、admin。
interface: 对外的 BFF 服务,接受来自用户的请求,比如暴露了 HTTP/gRPC 接口。
service: 对内的微服务,仅接受来自内部其他服务或者网关的请求,比如暴露了 gRPC 接口只对内服务。
admin:区别于 service,更多是面向运营测的服务,通常数据权限更高,隔离带来更好的代码级别安全。
job: 流式任务处理的服务,上游一般依赖 message broker。
task: 定时任务,类似 cronjob,部署到 task 托管平台中。
注意:
cmd 应用目录负责程序的: 启动、关闭、配置初始化等
DTO
项目的依赖路径为: model -> dao -> service -> api,model struct 串联各个层,直到 api 需要做 DTO 对象转换。
model: 放对应“存储层”的结构体,是对存储的一一隐射。
dao: 数据读写层,数据库和缓存全部在这层统一处理,包括 cache miss 处理。
service: 组合各种数据访问来构建业务逻辑。
server: 依赖 proto 定义的服务作为入参,提供快捷的启动服务全局方法。
api: 定义了 API proto 文件,和生成的 stub 代码,它生成的 interface,其实现者在 service 中。
service 的方法签名因为实现了 API 的 接口定义,DTO 直接在业务逻辑层直接使用了,更有 dao 直接使用,最简化代码。
DO(Domain Object): 领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。缺乏 DTO -> DO 的对象转换。
API 设计
gRPC 是什么
- 多语言:语言中立,支持多种语言。
- 轻量级、高性能:序列化支持 PB(Protocol Buffer)和 JSON,PB 是一种语言无关的高性能序列化框架。
- 可插拔
- IDL:基于文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub。
- 移动端:基于标准的 HTTP2 设计,支持双向流、消息头压缩、单 TCP 的多路复用、服务端推送等特性,这些特性使得 gRPC 在移动端设备上更加省电和节省网络流量。
gRPC 设计:
服务而非对象、消息而非引用:促进微服务的系统间粗粒度消息交互设计理念。
负载无关的:不同的服务需要使用不同的消息类型和编码,例如 protocol buffers、JSON、XML 和 Thrift。
流: Streaming API。
阻塞式和非阻塞式:支持异步和同步处理在客户端和服务端间交互的消息序列。
元数据交换:常见的横切关注点,如认证或跟踪,依赖数据交换。
标准化状态码:客户端通常以有限的方式响应 API 调用返回的错误。
API Project
为了统一检索和规范 API,我们内部建立了一个统一的 bapis 仓库,整合所有对内对外 API。
- API 仓库,方便跨部门协作。
- 版本管理,基于 git 控制。
- 规范化检查,API lint。
- API design review,变更 diff。
- 权限管理,目录 OWNERS。
配置管理
环境变量(配置)
Region、Zone、Cluster、Environment、Color、Discovery、AppID、Host,等之类的环境信息,都是通过在线运行时平台打入到容器或者物理机,供 kit 库读取使用。
静态配置
资源需要初始化的配置信息,比如 http/gRPC server、redis、mysql 等,这类资源在线变更配置的风险非常大,我通常不鼓励 on-the-fly 变更,很可能会导致业务出现不可预期的事故,变更静态配置和发布 bianry app 没有区别,应该走一次迭代发布的流程。
动态配置
应用程序可能需要一些在线的开关,来控制业务的一些简单策略,会频繁的调整和使用,我们把这类是基础类型(int, bool)等配置,用于可以动态变更业务流的收归一起,同时可以考虑结合类似 https://pkg.go.dev/expvar 来结合使用。
全局配置
通常,我们依赖的各类组件、中间件都有大量的默认配置或者指定配置,在各个项目里大量拷贝复制,容易出现意外,所以我们使用全局配置模板来定制化常用的组件,然后再特化的应用里进行局部替换。
包管理
Go 依赖管理是通过 Git 仓库模式实现,并随着版本的更迭已经逐渐完善
GOPATH 模式
GOPATH 目录是所有工程的公共依赖包目录,所有需要编译的 go 工程的依赖包都放在 GOPATH 目录下。
Vendor 特性
为了解决 GOPATH 模式下,多个工程需要共享 GOPATH 目录,无法适用于各个工程对于不同版本的依赖包的使用,不便于更新某个依赖包。 go 1.6 之后开启了 vendor 目录。
Go Module 包管理
从 Go1.11 以后开始支持 Module 依赖管理工具,从而实现了依赖包的进行升级更新,在 Go1.13 版本后默认打开。
go.mod
go.mod 文件中可以使用到的语法关键词以及含义:
- module: 定义当前项目的模块路径,不再赘述。
- go: 标识当前模块的 Go 语言版本,目前来看还只是个标识作用。
- require: 说明 Module 需要什么版本的依赖。
- exclude: 用于从使用中排除一个特定的模块版本。
- replace:替换 require 中声明的依赖,使用另外的依赖及其版本号。
为了解决 Go Modules 的包被篡改安全隐患,Go 开发团队在引入 go.mod 的同时也引入了 go.sum 文件,用于记录每个依赖包的哈希值,在构建时,如果本地的依赖包 hash 值与 go.sum 文件中记录得不一致,则会拒绝构建。
go.sum 文件中每行记录由 module 名、版本和哈希组成,并由空格分开;
在引入新的依赖时,通常会使用 go get 命令获取该依赖,将会下载该依赖包下载到本地缓存目录 $GOPATH/pkg/mod/cache/download,该依赖包为一个后缀为 .zip 的压缩包,并且把哈希运算同步到 go.sum 文件中;
在构建应用时,会从本地缓存中查找所有 go.mod 中记录的依赖包,并计算本地依赖包的哈希值 ,然后与 go.sum 中的记录进行对比,当校验失败,go 命令将拒绝构建。
GOPROXY
Go 1.13 将 GOPROXY 默认成了中国大陆无法访问的 https://proxy.golang.org,所以在国内需要配置代理进行使用。
GOPROXY 可以解决一些公司内部的使用问题:
访问公司内网的 git server
防止公网仓库变更或者消失,导致线上编译失败或者紧急回退失败
公司审计和安全需要
防止公司内部开发人员配置不当造成 import path 泄露
cache 热点依赖,降低公司公网出口带宽
Go private
GOPRIVATE 用来控制 go 命令把哪些仓库看做是私有的仓库,
这样的话,就可以跳过 proxy server 和校验检查,这个变量的值支持用逗号分隔,可以填写多个值,例如:
export GOPRIVATE=*.corp.example.com,github.com/org_name
goproxy.io
goproxy.io 是一个 Go Modules 开源代理,也可以作为公司内部代理。
下载以及编译
git clone https://github.com/goproxyio/goproxy.git
cd goproxy
go build
运行 goproxy 代理
./goproxy -listen=0.0.0.0:8081 -cacheDir=/tmp/cache -proxy https://goproxy.io -exclude “github.com/private”
References
https://www.ardanlabs.com/blog/2017/02/package-oriented-design.html
https://www.ardanlabs.com/blog/2017/02/design-philosophy-on-packaging.html
https://github.com/golang-standards/project-layout
https://github.com/golang-standards/project-layout/blob/master/README_zh.md
https://www.cnblogs.com/zxf330301/p/6534643.html
https://blog.csdn.net/k6T9Q8XKs6iIkZPPIFq/article/details/109192475?ops_request_misc=%7B%22request%5Fid%22%3A%22160561008419724839224387%22%2C%22scm%22%3A%2220140713.130102334.pc%5Fall.%22%7D&request_id=160561008419724839224387&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v28-6-109192475.first_rank_ecpm_v3_pc_rank_v2&utm_term=阿里技术专家详解DDD系列&spm=1018.2118.3001.4449
https://blog.csdn.net/chikuai9995/article/details/100723540?biz_id=102&utm_term=阿里技术专家详解DDD系列&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-0-100723540&spm=1018.2118.3001.4449
https://blog.csdn.net/Taobaojishu/article/details/101444324?ops_request_misc=%7B%22request%5Fid%22%3A%22160561008419724838528569%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=160561008419724838528569&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_click~default-1-101444324.first_rank_ecpm_v3_pc_rank_v2&utm_term=阿里技术专家详解DDD系列&spm=1018.2118.3001.4449
https://blog.csdn.net/taobaojishu/article/details/106152641
https://cloud.google.com/apis/design/errors
https://kb.cnblogs.com/page/520743/
https://zhuanlan.zhihu.com/p/105466656
https://zhuanlan.zhihu.com/p/105648986
https://zhuanlan.zhihu.com/p/106634373
https://zhuanlan.zhihu.com/p/107347593
https://zhuanlan.zhihu.com/p/109048532
https://zhuanlan.zhihu.com/p/110252394
https://www.jianshu.com/p/dfa427762975
https://www.citerus.se/go-ddd/
https://www.citerus.se/part-2-domain-driven-design-in-go/
https://www.citerus.se/part-3-domain-driven-design-in-go/
https://www.jianshu.com/p/dfa427762975
https://www.jianshu.com/p/5732b69bd1a1
https://www.cnblogs.com/qixuejia/p/10789612.html
https://www.cnblogs.com/qixuejia/p/4390086.html
https://www.cnblogs.com/qixuejia/p/10789621.html
https://zhuanlan.zhihu.com/p/46603988
https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/wrappers.proto
https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html
https://blog.csdn.net/taobaojishu/article/details/106152641
https://apisyouwonthate.com/blog/creating-good-api-errors-in-rest-graphql-and-grpc
https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
https://www.youtube.com/watch?v=oL6JBUk6tj0
https://github.com/zitryss/go-sample
https://github.com/danceyoung/paper-code/blob/master/package-oriented-design/packageorienteddesign.md
https://medium.com/@eminetto/clean-architecture-using-golang-b63587aa5e3f
https://hackernoon.com/golang-clean-archithecture-efd6d7c43047
https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1
https://medium.com/wtf-dial/wtf-dial-domain-model-9655cd523182
https://hackernoon.com/golang-clean-archithecture-efd6d7c43047
https://hackernoon.com/trying-clean-architecture-on-golang-2-44d615bf8fdf
https://manuel.kiessling.net/2012/09/28/applying-the-clean-architecture-to-go-applications/
https://github.com/katzien/go-structure-examples
https://www.youtube.com/watch?v=MzTcsI6tn-0
https://www.appsdeveloperblog.com/dto-to-entity-and-entity-to-dto-conversion/
https://travisjeffery.com/b/2019/11/i-ll-take-pkg-over-internal/
https://github.com/google/wire/blob/master/docs/best-practices.md
https://github.com/google/wire/blob/master/docs/guide.md
https://blog.golang.org/wire
https://github.com/google/wire
https://www.ardanlabs.com/blog/2019/03/integration-testing-in-go-executing-tests-with-docker.html
https://www.ardanlabs.com/blog/2019/10/integration-testing-in-go-set-up-and-writing-tests.html
https://blog.golang.org/examples
https://blog.golang.org/subtests
https://blog.golang.org/cover
https://blog.golang.org/module-compatibility
https://blog.golang.org/v2-go-modules
https://blog.golang.org/publishing-go-modules
https://blog.golang.org/module-mirror-launch
https://blog.golang.org/migrating-to-go-modules
https://blog.golang.org/using-go-modules
https://blog.golang.org/modules2019
https://blog.codecentric.de/en/2017/08/gomock-tutorial/
https://pkg.go.dev/github.com/golang/mock/gomock
https://medium.com/better-programming/a-gomock-quick-start-guide-71bee4b3a6f