本文参考 Go 微服务框架 go-kratos/kratos 的项目结构及相关最佳实践,Kratos 一套轻量级 Go 微服务框架,包含大量微服务相关功能及工具。
标准项目结构
/cmd
|-- cmd
|-- demo
|-- demo
+-- main.go
+-- demo1
|-- demo1
+-- main.go
项目的主干,每个应用程序目录名与可执行文件的名称匹配。该目录不应放置太多代码。
/internal
|-- internal
+-- demo
|-- biz
|-- service
+-- data
私有应用程序和库代码。该目录由 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
|-- memcache
+-- redis
|-- conf
|-- dsn
|-- env
|-- flagvar
+-- paladin
.
|-- docs
|-- example
|-- misc
|-- pkg
|-- third_party
|-- tool
外部应用程序可以使用的库代码。可以显式地表示该目录代码对于其他人而言是安全可用的。
/pkg 目录内可参考 Go 标准库的组织方式,按照功能分类。
/internal/pkg 一般用于项目内的跨应用公共共享代码,但其作用域仅在单个项目工程内。
pkg 和 internal 目录的相关描述可以参考 I’ll take pkg over internal[1]。
当根目录包含大量非 Go 组件和目录时,这也是一种将 Go 代码分组到一个位置的方法,使得运行各种 Go 工具更容易组织。
|-- cache
|-- memcache
| +-- test
+-- redis
+-- test
|-- conf
|-- dsn
|-- env
|-- flagvar
+-- paladin
+-- apollo
+-- internal
+-- mockserver
|-- container
|-- group
|-- pool
+-- queue
+-- apm
|-- database
|-- hbase
|-- sql
+-- tidb
|-- ecode
+-- types
|-- log
+-- internal
|-- core
+-- filewriter
应当为不同的微服务建立统一的 kit 工具包项目(基础库/框架)和 app 项目。
基础库 kit 为独立项目,公司级建议只有一个。由于按照功能目录来拆分会带来不少的管理工作,建议合并整合。
其具备以下特点:
.
|-- README.md
|-- api
|-- cmd
|-- configs
|-- go.mod
|-- go.sum
|-- internal
+-- test
/api
API 协议定义目录,比如 protobuf 文件和生成的 go 文件。
通常把 API 文档直接在 proto 文件中描述。
/configs
配置文件模板或默认配置。
/test
外部测试应用程序和测试数据。可随时根据需求构造 /test 目录。
对于较大的项目数据子目录是很有意义的。比如可使用 /test/data 或 /test/testdata(如果需要忽略目录中的内容)。
Go 会忽略以“.”或“_”开头的目录或文件,因此在命名测试数据目录方面有更大灵活性。
GitLab Project
|-- app
|-- replay
|--..
+-- member
|-- pkg
|-- database
|-- ..
+-- log
+-- ...
一个 GitLab project 中可以放置多个微服务 app(类似 monorepo),也可以按照 GitLab 的 group 里建立多个 project,每个 project 对应一个 app。
|-- cmd 负责程序的:启动、关闭、配置初始化等。
|-- myapp1-admin 面向运营侧的服务,通常数据权限更高,隔离实现更好的代码级别安全。
|-- myapp1-interface 对外的 BFF 服务,接受来自用户的请求(HTTP、gRPC)。
|-- myapp1-job 流式任务服务,上游一般依赖 message broker。
|-- myapp1-service 对内的微服务,仅接受来自内部其他服务或网关的请求(gRPC)。
+-- myapp1-task 定时任务服务,类似 cronjob,部署到 task 托管平台中。
以下这种目录结构风格:
|-- service
|-- api API 定义(protobuf 等)以及对应生成的 client 代码,基于 pb 生成的 swagger.json。
|-- cmd
|-- configs 服务配置文件,比如 database.yaml、redis.yaml、application.yaml。
|-- internal 避免有同业务下被跨目录引用了内部的 model、dao 等内部 struct。
|-- model 对应“存储层”的结构体,是对存储的一一映射。
|-- dao 数据读写层,统一处理数据库和缓存(cache miss 等问题)。
|-- service 组合各种数据访问来构建业务逻辑,包括 api 中生成的接口实现。
|-- server 依赖 proto 定义的服务作为入参,提供快捷的启动服务全局方法。
|-- ...
app 目录下有 api、cmd、configs、internal 目录。一般还会放置 README、CHANGELOG、OWNERS。
项目的依赖路径为:model -> dao -> service -> api,model struct 串联各个层,直到 api 做 DTO 对象转换。
另一种结构风格是将 DDD 设计思想和工程结构做了简化,映射到 api、service、biz、data 各层。
.
|-- CHANGELOG
|-- OWNERS
|-- README
|-- api
|-- cmd
|-- myapp1-admin
|-- myapp1-interface
|-- myapp1-job
|-- myapp1-service
+-- myapp1-task
|-- configs
|-- go.mod
|-- internal 避免有同业务下被跨目录引用了内部的 model、dao 等内部 struct。
|-- biz 业务逻辑组装层,类似 DDD domain(repo 接口再次定义,依赖倒置)。
|-- data 业务数据访问,包含 cache、db 等封装,实现 biz 的 repo 接口。
|-- pkg
+-- service 实现了 api 定义的服务层,类似 DDD application 处理 DTO 到 biz 领域实体的转换(DTO->DO),同时协同各类 biz 交互,不处理复杂逻辑。
松散分层架构(Relaxed Layered System):层间关系不太严格,每层都可能使用它下面所有层的服务(而不仅是下一层)。
每层都可能是半透明的,意味着有些服务只对上一层可见,而有些服务对上面的所有层都可见。
[ api ]
| | |
| [ service ] |
| | |
[ biz ]
| | |
[ data ]
继承分层架构(Layering Through Inheritance):高层继承并实现低层接口。需要调整各层顺序,将基础设施层移动到最高层。这依然是单向依赖,意味着领域层、应用层、表现层将不能依赖基础设施层,而基础设施层可以依赖它们。
[ data ]
| | |
| [ api ] |
| | |
[ service ]
| | |
[ biz ]
数据模型:
考虑服务应用对象初始化和生命周期管理,所有 HTTP/gRPC 依赖的前置资源初始化(包括 data、biz、service),之后再启动监听服务。```
资源初始化和关闭步骤繁琐,比较容易出错。可利用依赖注入的思路,使用 google/wire[2] 管理资源依赖注入,方便测试和实现单次初始化与复用。
svr := http.NewServer()
app := kratos.New()
app.Append(kratos.Hook{
OnStart: func(ctx context.Context) error {
return svr.Start()
},
OnStop: func(ctx context.Context) error {
return svr.Shutdown(ctx)
},
})
if err := app.Run(); err != nil {
log.Printf("app failed: %v\n", err)
return
}
另外还支持静态生成代码,便于诊断(而不是在运行时通过 reflection 实现)。
为了统一检索和规范 API,可在内部建立统一的仓库,整合所有对内对外 API(可参考 googleapis/googleapis[3]、envoyproxy/data-plane-api[4]、istio/api[5])。
gRPC[6] 是一种高性能的开源统一 RPC 框架:
syntax = "proto3";
package rpc_package;
service HelloWorldService {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
protoc --go_out=.--go_opt=paths=source_relative \
--go-grpc_out=.--go-grpc_opt=paths=source_relative \
helloworld/helloworld.proto
设计原则:
设计时不要过早关注性能问题,先实现标准化。
参考:
|-- bapis
|-- api
|-- echo
|-- v1
|-- echo.proto
|-- OWNERS 权限拥有者
|-- rpc
|-- status.proto 内部状态码
|-- metadata 框架元信息
|-- locale
|-- network
|-- device
|-- annotations 注解定义 options
|-- third_party 第三方引用
维护 API 需要注意总是保持向后兼容(非破坏性)的修改:
应避免破坏性的修改(一般需要修改 major 版本号):
包名为应用的标识(appid),用于生成 gRPC 请求路径或 proto 之间引用 Message。
文件中声明的包名称应该与产品和服务名称一致,带有版本的 API 的软件包名称必须以此版本结尾。
参考():
示例 | |
---|---|
产品名称 | Google Calendar API |
服务名称 | calendar.googleapis.com |
软件包名称 | google.calendar.v3 |
接口名称 | google.calendar.v3.CalendarService |
来源目录 | //google/calendar/v3 |
API 名称 | calendar |
请求 URL:/package_name.version.service_name/method
gRPC 默认使用 Protobuf v3 格式,去除了 required 和 optional 关键字(默认全部是 optional)。没有赋值的字段默认是基础类型字段的默认值,比如 0 或者 “”。
// proto2
message Account {
// 必须
required string name = 1;
// 可选,默认值改为 -1.0,有 haxXxx 方法。
optional double profit_rate = 2 [default=-1.0];
}
// proto3
message Account {
// 都是可选,默认值为 0 和 "",无 hasXxx 方法。
string name = 1
double profit_rate = 2;
}
将无法区分默认值或未赋值。因此在 Protobuf v3 中建议使用:wrappers.proto[7]。Wrapper 类型的字段即包装一个 message,使用时变为指针。
message DoubleValue {
double value = 1;
}
Protobuf 作为强 schema 约束的描述文件,也方便扩展,因此也可以用于配置文件定义。
首先由于会为服务监控带来麻烦,明确禁止在 HTTP Status Code 中统一设置为 200、在 Body 中再定义 code 字段标记具体错误类型的做法。
使用标准错误配合具体错误:比如服务端使用一个标准 google.rpc.Code.NOT_FOUND 错误代码告知客户端无法找到特定资源(大类:404,小类:具体资源)。
错误传播:如果 API 服务依赖于其他服务,不应盲目地将服务错误传播到客户端。在翻译错误时建议:
全局错误码 是松散、契约易被破坏的,应在每个服务传播错误时做一次翻译,保证每个服务 + 错误枚举是唯一的,定义在 proto 中(可作为文档)。
有时接口复用会带来歧义,比如一些字段给 A 方法用、另一些给 B 方法用;如果为不同方法定义 struct 又会造成冗余。
service LibraryService {
rpc UpdateBook(UpdateBookRequest) returns (Book);
}
message UpdateBookRequest { Book book = 1;}
message Book {
string name = 1;
string author = 2;
string title = 3;
bool read = 4;
}
gRPC 推荐的做法是利用 FieldMask 的部分更新:客户端可执行需要更新的字段信息,空 FieldMask 默认应用到所有字段。
service LibraryService {
rpc UpdateBook(UpdateBookRequest) returns (Book);
}
message UpdateBookRequest {
Book book = 1;
google.protobuf.FieldMask mask = 2;
}
通常包括以下内容:
配置传参先参考 net/http 库:
func main() {
s := &http.Server{
Addr: ":8080",
Handler: nil,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
log.Fatal(s.ListenAndServe())
}
缺点是无法获知修改公共字段是否会有副作用,字段的含义也要自行查阅文档。
改进是自行设计 config struct,建议使用 functional options:
type Server struct {
Addr string // required
Port int // required
Protocol string // not null, default TCP
Timeout time.Duration // not null, default 30
MaxConn int // not null, default 1024
TLS *tls.Config //
}
type Option func(*Server)
func Protocol(p string) Option {
return func(s *Server) {
s.Protocol = p
}
}
func Timeout(timeout time.Duration) Option {
return func(s *Server) {
s.Timeout = timeout
}
}
func MaxConn(maxConn int) Option {
return func(s *Server) {
s.MaxConn = maxConn
}
}
func TLS(tls *tls.Config) Option {
return func(s *Server) {
s.TLS = tls
}
}
func NewServerFP(addr string, port int, options ...Option) (*Server, error) {
// 有一个可变参数 options 可以传出多个上面的函数,for-loop 设置 Server 对象。
srv := Server{
Addr: addr,
Port: port,
Protocol: "tcp",
Timeout: 30 * time.Second,
MaxConn: 1000,
TLS: nil,
}
for _, option := range options {
option(&srv)
}
//...
return &srv, nil
}
func TestFunctionalOptions(t *testing.T) {
s1, _ := NewServerFP("localhost", 1024)
s2, _ := NewServerFP("localhost", 2048, Protocol("udp"))
s3, _ := NewServerFP("0.0.0.0", 8080, Timeout(300*time.Second), MaxConn(1000))
fmt.Println(s1, s2, s3)
}
在实践中应注意配置文件到配置数据之间映射的解耦:
[Config Web UI] <----+---------+
| ↓
[Config API] --------+--> [Config Data] ----> [System]
| ↑
[Config Language] <--+---------+
YAML:需要先转换成 JSON,再转成 Protobuf。Protobuf 的 Config 对象不能直接扩展方法,所以还需要加一个 Options 方法。
func ApplyYAML(s *redis.Config, yml string) error {
js, err := yaml.YAMLToJSON([]byte(yml))
if err != nil {
return err
}
return ApplyJSON(s, string(js))
}
// Options apply config to options.
func Options(c *redis.Config) []redis.Options {
return []redis.Options{
redis.DialDatabase(c.Database),
redis.DialPassword(c.Password),
redis.DialReadTimeout(c.ReadTimeout),
}
}
Protobuf:使用 wrap struct 区分是否有值。
syntax = "proto3";
import "google/protobuf/duration.proto";
package config.redis.v1;
// redis config.
message redis {
string network = 1;
string address = 2;
int32 database = 3;
string password = 4;
google.protobuf.Duration read_timeout = 5;
}
最终实现配置注入:
func main() {
// load config file from yaml.
c := new(redis.Config)
_ = ApplyYAML(c, loadConfig())
r, _ := redis.Dial(c.Network, c.Address, Options(c)...)
}
实现代码变更系统功能是冗长且复杂的过程,往往还涉及 CR、测试等流程。而更改单个配置选项也可能对功能产生重大影响,且通常情况下修改配置还容易被忽略、未经测试就上线。
配置管理的目标:
模块管理
Go 依赖管理是通过 Git 仓库模式实现,并随着版本的更迭逐渐完善。
早期是 GOPATH 模式:GOPATH 目录是所有工程的公共依赖包目录,所有需要编译的 go 工程的依赖包都放在 GOPATH 目录下。
后续引入多版本支持的 Vendor 特性:go 1.6 之后开启了 vendor 目录,以支持各个工程对于不同版本的依赖包使用的需求(每个工程拷贝一份代码)。
Go Module 管理:Go1.11 实现了依赖包的升级更新,在 Go1.13 版本后默认打开。
GOPATH
GOPATH 为 Go 开发环境时所设置的一个环境变量。
历史版本的 go 语言开发时,需要将代码放在 GOPATH 目录的 src 文件夹下。go get 命令获取依赖,也会自动下载到 GOPATH 的 src 下。
以下命令会将代码下载到 $GOPATH/src/github.com/foo/bar。
go get github.com/foo/bar
GOPATH 具体结构如下,必须包含三个文件夹:
GOPATH
|-- bin 二进制文件
|-- pkg 预编译文件(加快后续编译速度)
|-- src 源代码
|-- github.com
GO Modules
从 Go 1.11 开始初步支持,解决了依赖版本的信息管理,并且保证安全性 。
由 go.mod 和 go.sum 组成,包括依赖模块路径定义,通过 checksum 保证包的安全性,并且可以在 GOPATH 外创建和编译项目。
使用 go mod init 命令初始化项目,生成 go.mod 文件:
go mod init example.com.hello
cat go.mod
module example.com/hello
go 1.16
使用 go get github.com/sirupsen/logrus 可下载或更新依赖包:
module example.com/hello
go 1.16
require github.com/sirupsen/logrus v1.8.1
各关键字含义:
Checksum
为解决 Go Modules 的包被篡改的安全隐患,引入 go.sum 文件以记录每个依赖包的哈希值,在构建时如果本地的依赖包 hash 值与 go.sum 文件中记录的不一致,则会拒绝构建。
Proxy
Go 1.13 的 GOPROXY 默认为 https://proxy.golang.org,在国内需要配置代理才能使用。GOPROXY[9] 也可以解决公司内部的使用问题:
export GOPROXY=https://goproxy.io,direct
# 不走 proxy 的私有仓库或组,以逗号分隔。
export GOPRIVATE=git.mycompany.com,github.com/my/private
Private
用于控制 go 命令把某些仓库视作私有仓库,可以跳过 proxy server 和 checksum 检查,GOPRIVATE 的值同时作为 GONOPROXY 和 GONOSUMDB 默认值:
# 以逗号分隔。
export GOPRIVATE=*.corp.example.com,github.com/org_name
推荐同时配置 GOPROXY 和 GOPRIVATE 使用,GOPRIVATE 也可以识别 Git SSH KEY 进行权限效验。
GOPROXY 编译部署
goproxy.io 是 Go Modules 开源代理,也可作为公司内部代理。
# 下载编译:
git clone https://github.com/goproxyio/goproxy.git
cd goproxy
go build
# 运行代理:
# ./goproxy -listen=0.0.0.0:8081 -cacheDir=/tmp/cache -proxy https://goproxy.io -exclude "github.com/private"
#
# -cacheDir 指定 Go 模块的缓存目录
# -exclude proxy 模式下指定不经过上游服务器的 path
# -listen 服务监听端口,默认 8081
# -proxy 指定上游 proxy server,推荐 goproxy.io
访问内网 Git 仓库:
[url "[email protected]:"]
insteadOf = https://github.com/
[url "[email protected]:"]
insteadOf = https://gitlab.com/
小型测试带来优秀的代码质量、良好的异常处理、优雅的错误报告;大中型测试会带来整体产品质量和数据验证。
不同类型的项目对测试的需求不同,总体上有 70/20/10 经验法则:70% 小型测试,20% 中型测试,10% 大型测试。
如果一个项目是面向用户的,拥有较高的集成度或用户接口比较复杂,就应该有更多的中型和大型测试;如果是基础平台或者面向数据的项目(例如索引或网络爬虫),则最好有大量的小型测试。
单元测试
单元测试的基本要求:
基于 docker-compose 实现跨平台跨语言环境的容器依赖管理方案,以解决运行 unittest 场景下的容器依赖问题:
|-- service
|-- api
|-- cmd
|-- configs
|-- internal
|-- test
|-- docker-compose.yaml
|-- database.sql
要满足以下原则:
func TestMain(m *testing.M) {
flag.Set("f", "./test/docker-compose.yaml")
flag.Parse()
if err := lich.Setup(); err != nil {
panic(err)
}
defer lich.Teardown()
if ret := m.Run(); ret != 0 {
panic(ret)
}
}
最佳实践
利用 go 官方提供的 Subtests + Gomock 完成整个单元测试。对于每层代码:
一般的开发测试流程:
参考
[1]
I’ll take pkg over internal: https://travisjeffery.com/b/2019/11/i-ll-take-pkg-over-internal/
[2]
google/wire: https://github.com/google/wire
[3]
googleapis/googleapis: https://github.com/googleapis/googleapis
[4]
envoyproxy/data-plane-api: https://github.com/envoyproxy/data-plane-api
[5]
istio/api: https://github.com/istio/api
[6]
gRPC: https://grpc.io/
[7]
wrappers.proto: https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/wrappers.proto
[8]
expvar: https://pkg.go.dev/expvar
[9]
https://proxy.golang.org,在国内需要配置代理才能使用。GOPROXY: https://proxy.golang.xn–org%2C-ux8is5bfzj85qrnap81iff2ccld904crhfz75bsl3a8vw.goproxy/
[10]
Package Oriented Design (ardanlabs.com): https://www.ardanlabs.com/blog/2017/02/package-oriented-design.html
[11]
Design Philosophy On Packaging (ardanlabs.com): https://www.ardanlabs.com/blog/2017/02/design-philosophy-on-packaging.html
[12]
golang-standards/project-layout: Standard Go Project Layout (github.com): https://github.com/golang-standards/project-layout
[13]
浅析VO、DTO、DO、PO的概念、区别和用处 - 随风而逝,只是飘零 - 博客园 (cnblogs.com): https://www.cnblogs.com/zxf330301/p/6534643.html
[14]
阿里技术专家详解 DDD 系列- Domain Primitive_chikuai9995的博客-CSDN博客: 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
[15]
阿里技术专家详解DDD系列 第三讲 - Repository模式_淘系技术-CSDN博客: https://blog.csdn.net/taobaojishu/article/details/106152641
[16]
Errors | Cloud APIs | Google Cloud: https://cloud.google.com/apis/design/errors
[17]
贫血,充血模型的解释以及一些经验_知识库_博客园 (cnblogs.com): https://kb.cnblogs.com/page/520743/
[18]
领域驱动设计 实践手册(1.Get Started) - 知乎 (zhihu.com): https://zhuanlan.zhihu.com/p/105466656
[19]
DDD 实践手册(2. 实现分层架构) - 知乎 (zhihu.com): https://zhuanlan.zhihu.com/p/105648986
[20]
DDD 实践手册(3. Entity, Value Object) - 知乎 (zhihu.com): https://zhuanlan.zhihu.com/p/106634373
[21]
DDD 实践手册(4. Aggregate — 聚合) - 知乎 (zhihu.com): https://zhuanlan.zhihu.com/p/107347593
[22]
DDD 实践手册(5. Factory 与 Repository) - 知乎 (zhihu.com): https://zhuanlan.zhihu.com/p/109048532
[23]
DDD 实践手册(6. Bounded Context - 限界上下文) - 知乎 (zhihu.com): https://zhuanlan.zhihu.com/p/110252394
[24]
01、DDD和微服务的关系 - 简书 (jianshu.com): https://www.jianshu.com/p/dfa427762975
[25]
Domain Driven Design in Go – Citerus: https://www.citerus.se/go-ddd/
[26]
Domain Driven Design in Go: Part 2 – Citerus: https://www.citerus.se/part-2-domain-driven-design-in-go/
[27]
Domain Driven Design in Go: Part 3 – Citerus: https://www.citerus.se/part-3-domain-driven-design-in-go/
[28]
简书 (jianshu.com): https://www.jianshu.com/p/5732b69bd1a1
[29]
领域驱动设计系列文章(1)——通过现实例子显示领域驱动设计的威力 - Cat Qi - 博客园 (cnblogs.com): https://www.cnblogs.com/qixuejia/p/10789612.html
[30]
领域驱动设计系列文章(2)——浅析VO、DTO、DO、PO的概念、区别和用处 - Cat Qi - 博客园 (cnblogs.com): https://www.cnblogs.com/qixuejia/p/4390086.html
[31]
领域驱动设计系列文章(3)——有选择性的使用领域驱动设计 - Cat Qi - 博客园 (cnblogs.com): https://www.cnblogs.com/qixuejia/p/10789621.html
[32]
区分 Protobuf 中缺失值和默认值 - 知乎 (zhihu.com): https://zhuanlan.zhihu.com/p/46603988
[33]
protobuf/wrappers.proto at master · protocolbuffers/
protobuf (github.com): https://github.com/protocolbuffers/
protobuf/blob/master/src/google/protobuf/wrappers.proto
[34]
Functional options for friendly APIs – The acme of foolishness (cheney.net): https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
[35]
command center: Self-referential functions and the design of options: https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html
[36]
Creating Good API Errors in REST, GraphQL and gRPC | APIs You Won’t Hate - A community that cares about API design and development. (apisyouwonthate.com): https://apisyouwonthate.com/blog/creating-good-api-errors-in-rest-graphql-and-grpc/
[37]
Clean Coder Blog: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
[38]
GopherCon 2018: Kat Zien - How Do You Structure Your Go Apps - YouTube: https://www.youtube.com/watch?v=oL6JBUk6tj0
[39]
zitryss/go-sample: Go Project Sample Layout (github.com): https://github.com/zitryss/go-sample
[40]
paper-code/packageorienteddesign.md at master · danceyoung/paper-code (github.com): https://github.com/danceyoung/paper-code/blob/master/package-oriented-design/packageorienteddesign.md
[41]
Clean Architecture using Golang. Update | by Elton Minetto | Medium: https://eminetto.medium.com/clean-architecture-using-golang-b63587aa5e3f
[42]
Standard Package Layout. Addressing one of the biggest technical… | by Ben Johnson | Medium: https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1
[43]
410 Deleted by author — Medium: https://medium.com/wtf-dial/wtf-dial-domain-model-9655cd523182
[44]
Trying Clean Architecture on Golang | Hacker Noon: https://hackernoon.com/golang-clean-archithecture-efd6d7c43047
[45]
Trying Clean Architecture on Golang — 2 | Hacker Noon: https://hackernoon.com/trying-clean-architecture-on-golang-2-44d615bf8fdf
[46]
Applying The Clean Architecture to Go applications • Manuel Kießling (kiessling.net): https://manuel.kiessling.net/2012/09/28/applying-the-clean-architecture-to-go-applications/
[47]
katzien/go-structure-examples: Examples for my talk on structuring go apps (github.com): https://github.com/katzien/go-structure-examples
[48]
Ashley McNamara + Brian Ketelsen. Go best practices. - YouTube: https://www.youtube.com/watch?v=MzTcsI6tn-0
[49]
DTO to Entity and Entity to DTO Conversion - Apps Developer Blog: https://www.appsdeveloperblog.com/dto-to-entity-and-entity-to-dto-conversion/
[50]
I’ll take pkg over internal (travisjeffery.com): https://travisjeffery.com/b/2019/11/i-ll-take-pkg-over-internal/
[51]
wire/best-practices.md at main · google/wire (github.com): https://github.com/google/wire/blob/main/docs/best-practices.md
[52]
wire/guide.md at main · google/wire (github.com): https://github.com/google/wire/blob/main/docs/guide.md
[53]
Compile-time Dependency Injection With Go Cloud’s Wire - The Go Blog (golang.org): https://blog.golang.org/wire
[54]
google/wire: Compile-time Dependency Injection for Go (github.com): https://github.com/google/wire
[55]
Integration Testing in Go: Part I - Executing Tests with Docker (ardanlabs.com): https://www.ardanlabs.com/blog/2019/03/integration-testing-in-go-executing-tests-with-docker.html
[56]
Integration Testing in Go: Part II - Set-up and Writing Tests (ardanlabs.com): https://www.ardanlabs.com/blog/2019/10/integration-testing-in-go-set-up-and-writing-tests.html
[57]
Testable Examples in Go - The Go Blog (golang.org): https://blog.golang.org/examples
[58]
Using Subtests and Sub-benchmarks - The Go Blog (golang.org): https://blog.golang.org/subtests
[59]
The cover story - The Go Blog (golang.org): https://blog.golang.org/cover
[60]
Keeping Your Modules Compatible - The Go Blog (golang.org): https://blog.golang.org/module-compatibility
[61]
Go Modules: v2 and Beyond - The Go Blog (golang.org): https://blog.golang.org/v2-go-modules
[62]
Publishing Go Modules - The Go Blog (golang.org): https://blog.golang.org/publishing-go-modules
[63]
Module Mirror and Checksum Database Launched - The Go Blog (golang.org): https://blog.golang.org/module-mirror-launch
[64]
Migrating to Go Modules - The Go Blog (golang.org): https://blog.golang.org/migrating-to-go-modules
[65]
Using Go Modules - The Go Blog (golang.org): https://blog.golang.org/using-go-modules
[66]
Go Modules in 2019 - The Go Blog (golang.org): https://blog.golang.org/modules2019
[67]
Testing with GoMock: A Tutorial - codecentric AG Blog: https://blog.codecentric.de/en/2017/08/gomock-tutorial/
[68]
gomock · pkg.go.dev: https://pkg.go.dev/github.com/golang/mock/gomock
[69]
A GoMock Quick Start Guide. An opinionated tutorial for unit… | by Che Dan | Better Programming: https://betterprogramming.pub/a-gomock-quick-start-guide-71bee4b3a6f1?gi=e44758036c10