我们接下来了解一下Go工程化里面的包管理
包管理
其实Go语言随着它的版本的演进
经历了好几代
之前其实被吐槽得非常厉害
它谷歌内部叫motor report就是大仓库
那么大仓库的方式
因为所有代码都是在一起
所以它早期的版本管理比较简单
也是因为它内部的需求
所以跟开源世界
或者跟我们其他公司的整个版本管理也好
或者代码管理也好
有很大的差异
导致它很多这种feature
不是像那种开源社区的那种方式去演进
我们看一下
Go的整个一代管理是通过Git仓库模式来实现的
并且随着版本的更迭
到现在其实相对来说已经比较完善了
最早期我开始学的时候就是GOPATH模式
GOPATH模式是
所有工程的公共依赖包目录全部放在里面
然后需要编译的Go工程的依赖包放在GOPATH目录下
为什么
因为Go内部其实它就是一个大仓库
整个全部在一起的
所以它不需要版本管理
它所有的只有一个统一的版本
当然如果大家感兴趣
我找时间可以讲一下
谷歌的叫模特rap这个概念
因为我们刚好之前在公司里面也是按这种方式去实践过一段时间
那么之后在Go1.6引入了一个叫Vendor
Vendor的一个特性
就为了解决GOPATH模式下
多个工程需要共享GOPATH目录
因为多个工程共GOPATH目录
因为大家每一个对一个库的版本
就是依赖是有区别的
那么说不便于更新某一个依赖包
1.6之后开启了一个Vendor目录
这个模式说白了
把代码拷一份
copy拷到你的工程里面
是不是有点恶心
因为这样当然就解决了这个版本问题
因为你拷的是1.3
他拷的1.4
大家都在自己的Vendor目录下 对吧
其实是叫Vendor一个特性
那么到现在1.11之后
Go Module的整个包管里
它开始支持了所谓的模块依赖管理工具
从而实现了依赖包的一个可以进行升级更新
那么在go1.13版本以后它就默认打开了
那么GOPATH为Go开发环境时所设置的一个环境变量
很多人就被GOPATH给堵住了学Go的时候
我们简单理解GOPATH以前理解为是个工作空间
先要设置GOPATH
然后把需要的代码放在GOPATH的src目录下
然后通过go get命令获取
把它自动下载到GOPATH/src的目录下
举个例子来说
比方go get github.com/foo/bar
它会把代码下载到GOPATH的src下的github.com/foo/bar里面去
它的目录结构其实内部分为几个
一个是编译生成的二进制文件
也就是说你的go install
然后package就是预编译的一些文件
以加速程序的后续编译
就是你编过的话
它就会.a文件就会cache在这个目录下
然后src就是所有的源代码
比方说我们刚刚github.com/foo/bar
它就会在github.com这个里面 对
它就会在这个里面
那么到了1.11
我们刚才说的Go Module是从1.11开始初步支持
它解决了一个依赖版本的信息管理
并且保证安全性
主要是通过go.mod的文件和go.sum文件两个组成
它们主要包含就是依赖模块的一个路径定义
已经通过checksum的方式进行保证包的安全性
因为你下载一个东西
万一没有一个
我万一把里面的东西篡改了
其实就有可能会引入一些风险
它相对来说没那么安全
并且首次可以在GOPATH外
终于可以进行创建和编译项目
你再也不需要依赖GOPATH
去把所有的你的所谓的work space
你的工作目录放到GOPATH/src目录下
我们其实只要随便建一个目录
比方我建了一个hello目录
我建好hello进去以后
只要敲go mod init example.coom/hello
它就会创建一个go.mod的文件
然后内容就是一个module example.com/hello
它的package的名字
然后里面还会写上它的Go版本的一个依赖
就像这样的代码一样
比方我依赖了一个logrus的包
你很方便的通过get就可以下载或者是更新依赖包
并且你在go.mod里面通过指定它的版本号
那么也可以通过这样的一个require加一个路径
就Github的一个地址加上一个版本号
你就可以下载具体的一个包
你可以通过go build的方式
或者go get的方式
或者go mod的工具的方式
都可以去更新或者下载依赖包
相对来说是比较方便的
那么go.mod的文件中几个关键语法
首先定义当前项目的模块路径
就是这个地方
然后Go的版本
然后你需要对某一个第三方库的依赖的路径和版本号
然后你可以排除某一个路径
然后你可以用replace
举个例子
我们以前内部用Gitlab的时候
我们导入的包名称可能内部叫kratos
可能就是直接kratos
现在因为我们把它推到了Gitlab或者推到了Github上
我假设想做一个就是replace的话
我就可以指定kratos映射成Github的某一个路径
然后加上一个版本号
这样的话我可以做一个替换require中的声明的依赖
使用另外的依赖机器版本号
这个是go.mod的一个基本的一些关键字和语法
然后Go Modules它为了解决包被篡改的一个安全隐患
Go当时开发团队里面
它的开发团队引入了一个go.mod的同时也引入了go.sum文件
用来记录每一个包的哈希值
在构建的时候
如果本地的依赖包的哈希值与go.sum文件记录的不一致
它就会拒绝
就go build会失败
我就不展开讲了
因为它就相当于是在下载过程中计算一些校验和
这个大家可以自己看一下
关键是我们要解决什么
我们在公司内怎么去使用
那么go1.13将GOPROXY
我们这里有个环境变量叫GOPROXY
默认成了中国大陆无法访问的一个路径
就这个地址经常访问不通
经常被墙
所以在国内你要去下载
你就要去做加速
你需要配置代理来进行使用
那么GOPROXY
这种配置它可以解决一些问题
它可以解决什么问题呢
你通过GOPROXY你可以干嘛
比方说我们知道这个路径被墙了
你可以指定一个比方说goproxy.cn的一个路径
它现在有好多地址有些是被墙的
有些是国内的代理
GOPROXY的引入可以解决一些什么问题
第一访问公司内网的git server
因为不是所有代码都是在Github上的
第二防止公网仓库变更或者消失导致线上编译失败
因为你架一层代理的话
它可以做一些cache
第三就是说你自己架个GOPROXY的话
它可以公司的审计和安全的需要
另外就是说防止公司内部开发人员配置不当
导致import path泄露
举个例子
这个地方什么意思呢
之前我也不太熟悉 后来问一下同事
假设你引入了一个叫a.cn
a.cn的一个路径
a.cn路径在公网并没有
可能是你内网的一个地址
然后你go build的时候
它就会把这个请求forward到这个路径
就是proxy.golang.org
然后最后最终再转发到a.cn这个地址
那么如果有人恶意的a.cn伪造了你的包的路径的地址
提供一些那种什么假项目
然后让你可以编译通过
你就坑了 对吧
万一里面有一个什么删除
rm -rf /*这种代码你不就坑了吗
所以这个其实是要防止这种import path被泄露到公网去
因为这个地址可能存在可能不存在
还有说做一下
cache的一些热点的一些依赖
因为你经常使用的库
你如果被缓存到GOPROXY里面的话
可以降低公网出口的带宽
说白了做个加速
配置GOPROXY也非常简单
我们可以直接设置在我们的Linux bash或者是shell里面
配置一下GOPROXY的一个代理的地址
比方说goproxy.io 对吧
direct是回源的意思
可以回源到原始的Github
然后配置一个GOPRIVATE
配置一个Go私仓的一个地址
那么你可以只用逗号分隔
表示哪一些私仓可以跳过GOPROXY
跳过checksum
GOPRIVATE它这个变量
用来控制go命令把哪些仓库看作是私有的仓库
那么这样的话就可以跳过我们proxy server和校验和
就是checksum的一个检测
那么你可以用逗号去分隔
然后它也支持一些通配符
比方说我们内网有一些私有的仓库地址
你就可以把它配在这个地方
你配在这个地方
当配置GOPRIVATE的时候
它的值同时也将作为GONOPROXY和GONOSUMDB的默认值
所以当配置GOPRIVATE的后
它将跳过代理以及跳过校验
所以我们最推荐的一种姿势就是配置GOPROXY
然后如果你有私有内网的一些仓库再配置GOPRIVATE的值进行使用
那么GOPRIVATE这个值也可以识别Git SSH KEY来进行一些权限的校验
也就是说你可以走原始的GIT SSH KEY来进行连到你公司内网得Git
这样的话我觉得是一个标准的
在公司内网去使用的一种模型
既使用GORPROXY来做加速
同时使用GOPRIVATE来跳过私仓
跳过你们公司内部的私仓
goproxy.io也是一个开源的代理
我们可能是作为公司内部的一个代理
因为我们自己公司内部得代理
就方便做什么
方便做加速 对吧
那当然还有一些其他的好处
整个是它的编译和运行其实都非常简单
它可以配置说模块的缓存目录
哪些跳过上游
比方说私仓可能要跳过上游
监听端口
还可以指定上游的proxy
比方说它可以推荐用goproxy.io
那么这个其实是大家经常用的一个goproxy.io的地址
我们再看一下GOPROXY配置访问公司内网的Git server怎么配
它只需要在用户本地配置GONOSUMDB跳过私仓
然后同时goproxy server的配置exclude进行排除掉你代理的仓库
然后goproxy server你可以配置SSH Key
并将在仓库添加只读权限
就是说你可以在goproxy server上配置SSH Key
去连到你的内网的私仓
然后goproxy server的配置点.gitconfig
它要做把原来SSH Key的认证方式替换成https的方式
因为它支持只读
你要用https的方式去下载
那么讲到这里
其实可以自己搭一个goproxy server用来做公司内容的加速
可能找一台机器可以翻墙或者通过连大陆的一些这种能访问到的一些proxy
然后你可以做自己内网
相当于加速
因为内网拖总比每次到公网去下载肯定要快得多 对吧
这是第一个
你可以跳过一些私仓的
你自己可以去使用
那么你只要配一个GOPROXY就搞定了
但是GOPROXY没有搞定什么呢
如果你的私仓
它不是说大家都有权限的
它不是一个公共仓库
可能是a部门用a的b部门用b的 对吧
那么这种情况下就要使用goproxy.io的这个proxy server
加上GOPRIVATE变量去跳过一些只有你有权限的
因为只有跳过只有你有权限的那些仓库
它会用默认的Git SSH Key这种方式去连
而不是走https
因为https的话它没有带身份信息
所以说下载会失败
这样的话就能解决公司内网公共仓库和公司内网私有仓库
用GOPRIVATE
一个是做proxy server
走一个exclude进行跳过
那么这个相对来说是一个比较完美的一个解决方案
但是不是最佳的解决方案
那么有一篇文章是讲腾讯他们内部的GOPROXY的思路
简单来说虽然我们公司内网私仓跳过了checksum
它仍然是存在一个风险的
举例来说
比方说有个同学打了一个1.0版本
他恶意的又去push了一些代码
又把以前的tag删掉
又打了个1.0版本
那么其实两个版本就会diff 对吧
那么这个时候有可能就checksum可能因为你跳过了
那么其实可以编译通过
因为你跳过了
最终1.0相当于两个代码
但是你最终用哪个不清楚
它是不清楚的
那么其实就存在风险
构建被破坏了
那么腾讯它这个思路我看了一下它是怎么做
内网的goproxy server他们做了一些功能的改进
比方说我刚才说通过GOPRIVATE这种跳过的思路
他也解决了
他怎么做呢
你找到他们的goproxy server界面
你可以用你的Git的账号填进去生成一个URL
里面会用user@password然后加上URL的路径
说白了就是一个标准的HTTP basic的一个authentication
就是一个基础的验证
就是账号密码的验证信息
带一个这样的token
加上一个URL
配proxy的时候就是配那个地址
那么这样的话
你通过你这个地址连到腾讯的goproxy server
它内部相当于
我猜测它应该是模拟成Git SSH
找到你的身份信息
然后去提交到内网的Git server
然后这样的话他就可以自己实现一个
SumDB做个checksum
所以我认为他这个思路也算是比较好的一个思路
当然我们其实如果要求没有这么高
就用普通的goproxy.io加上一个GOPRIVATE
基本上能解决大部分问题
这块相对比较简单
我们内容也比较短一点啊
说白了其实还是没有非常好用
如果去搜网上的文章的话
吐槽go mod的文章非常多
什么十宗罪之类的
大家也可以去看一看它目前存在一些什么样的风险和问题
这里面都是我找到的官方blog的一些公布的一些文章
大家还是要去阅读一下
它基础的使用的一些姿势
我们下一节就是要了解一下
就单元测试或者说Go的整个工程化的测试是怎么做的
那么这个东西
我们先看一下单元测试
首先在谷歌内部
有一本书叫《谷歌测试之道》
这本书建议大家有兴趣可以去阅读一下
他们的内部的一些测试方法论
我们从整个测试里面来看
整个生态来看
小型测试写的代码是写得非常多的
它的速度其实怎么讲
小单元测试因为你跑起来case非常多对吧
它其实是非常花时间的
因为太多了
那么越往上面在中型测试大型测试
越往上走我们对系统的信心就越足 对吧
因为你去跑一个完整的业务场景的这种测试
你跑一个案例的话
它是测一个场景
相对来说你没那么虚
但是大型测试它对环境的依赖就越来越复杂
你可能需要一个完整的测试环境
依赖各种微服务
你才能把整个场景给跑出来 对吧
所以它对要求非常高
那么通常来说小型测试带来的是优秀的代码质量
因为你写单元测试保证的是你的函数本身的稳定性 对吧
良好的异常处理
优雅的错误报告
大中型测试会带来整体产品质量和数据验证
就说你这个场景可以覆盖得比较好
那么不同类型的项目对测试的需求不同
总体上来说
你像我们如果做基础设施的话
其实需要大量的小型测试
以及需要大量的大型测试
那么你如果是做业务的话
其实端到端测试可能会偏多
那么大型测试可能会写的少
基本上按照一个721的原则
70%是小型测试
20%是中型测试
10%是大型测试
如果那个项目是面向用户端的
我觉得需要较高的集成度
或者用户接口比较复杂
它应该更多的做大型测试
因为你测场景比测单元测试带来的收益要大得多
如果是基础平台
刚刚说的那种底层的一些东西
可能做小型测试会好得多
因为中大型测试可能会少很多
因为你需要每一个方法能保证它可靠性
所以这个是我们的对测试的到底写在哪里比较关键
我的想法是什么呢
我们的单元测试是开发人员写
当时我们的test case就是
我们的大型测试测API其实也是开发应该写的
因为只有开发人员才搞得清楚
整套你的API应该怎么使用
应该它的正向或者逆向的一些case是什么样的
你的case设计应该是你来做的
所以同学可以说那测试同学不就失业了
测试同学应该提供良好的测试平台
比方我们内部会用像类似yapi
来管理很多test case
然后我们单元测试其实主要还是用gotest原生的一个test框架
它的整个gotest就非常简单了
基本上非常好用 对吧
gotest什么也不依赖
那么大型测试我们就用yapi
来针对API级别的一个测试
那么测试环境的管理
我们之前讲Go微服务的多租户的时候
其实讲过通过染色或者通过多租户
你可以虚拟出多套的测试环境出来
那么这样的话我们做集成测试也好
做大型的测试回归也好
都会非常方便
包括你做功能测试也好
因为你有多套FAT所以也是非常方便的
《谷歌测试之道》这本书里面提到一个点叫
自动化实现的用于一个单独函数或独立功能模块的代码
是否按照预期工作
着重于典型的功能性问题
数据损坏 错误条件 大小差一错误
大小差一叫off-by-one
它指的是一种常见的程序设计的错误
等各种方面的一些验证
那么单元测试其实有些要求我简单提一下
第一要跑得非常快
第二它的环境应该是一致的
就是你的单元测试跑之前和跑之后
对环境的依赖不会有一些数据污染
你比方说跑一次添加好友
你已经添加过这个用户报过错
你的整个case就被破坏了 对吧
应该是测试前后要把什么
把环境给初始化或者把环境给还原掉
另外就是说我们写单元测试的时候
每一个写了好多的TestXxxx...
它们应该可以被以任何顺序执行都不会出问题
另外每一个TestXxxx的方法可以被并行执行
因为我要保证效率
所以它有可能会要并行去跑
所以这是单元测试的一些基础的要求
那么 很核心的一个问题是什么呢
单元测试就是我们的目录结构里面data层
我们先想你的biz层依赖repo的接口
那么你的repo接口为什么要做接口
我可以做个mock
然后我的biz层业务逻辑层就可以完整的自己跑
使用gomoke加gotest
你基本上就可以自己去做一个mock掉这个repo
然后你可以把整个代码去跑起来
那么api层因为有gRPC的client或者server的interface
其实这一块你的service层去模拟其实也是非常简单的
最麻烦是什么大家知道吗
其实是基础设施层
或者叫data层怎么去做单元测试
我认为以前大家经常容易想到
我是不是把msql mock掉
但是大家知道吗
mysql要去mock掉
或者说你依赖hbase
依赖于别的一些基础设施
kafka之类的
你要把它整个功能mock出来真的非常复杂
而且一般这种mock的代码都是跟语言绑定的 对吧
比方说Java有一个Java的
有个Java的mysql mock
但是你可能就够也要去写一个
那么Python可能需要写一个
那么不一定所有的SDK都有抽象
或者说不一定都很方便去做
in-process就在内层内模拟一个mysql
这种实现真的非常复杂
那么这种in-process的这种模拟
我自己认为有非常多的弊端
虽然你可以说把它的接口去mock
去写个select
我觉得这种真的非常不靠谱
所以直到Docker出来
我们以前在公司内部真的很挫
我们怎么做
我们是依赖一个统一的测试环境
但是这个环境被好多人使用
可以想象到很容易被破坏 对吧
而且你想你带着笔记本
你要写单元测试
你可能去星巴克划个水
但是你这个时候你的单元测试还跑不了
你还得通过VPN连到公司才能跑
肯定不靠谱
所以后来我知道Docker出来以后
包括docker-compose的方案
很容易去实现跨平台语言环境的一个容器依赖管理方案
就是docker-compose是跨平台的跨语言的环境的一个管理 对吧
那么对于解决Unittest来说
我认为非常简单
第一你知道本地装Docker
无侵入式的环境初始化
你起一个Docker多简单
然后你可以快速的重置环境
因为你每次整个容器卸载掉
再重新拉起就整个初始化掉了 对吧
那么可以随时随地运行
你可以在任何地方甚至没有网的情况下
你都可以去在本地起个mysql起个redis
那么不依赖任何其他的外部服务
也可以跑出整个环境出来 非常简单
如果你是依赖的外部的gRPC
你可能gRPC的client需要mock掉
你把整个client mock掉
另外就是说通过docker-compose
讲清楚你需要哪些资源
另外最核心的一个点docker-compose它是一个真实的外部依赖
你写的SQL是真真实实的跑进去
数据什么事物各种支持
它都是一个完整的
那么你在写case的时候是绝对不会出错的
它是一个很真实的环境的一种模拟
我认为是比较爽的
所以我们当时的思路是什么
在我们的test目录下
有一个docker-compose的一个YAML文件
它会描述出我们依赖的资源
比方说mysql大概是它的一个版本号
然后依赖的resis
映射到你物理机的一个端口号
然后交由我们的app
有两种思路
一种是你自己手动的
用docker-compose把环境拉起来
那么我们test目录里面还会有一些资源
我们知道所谓的初始化的一些资源脚本
比方说建库建表初始化一些数据
然后因为你数据肯定要做reset 对吧
可能需要各种一些测试的一些数据
那么你可以销毁容器的时候
下次再拉起又是一个干净的数据库
那么你要重新建库建表导入数据对吧
这个是第一种
第二种可能是你把docker-compose拉起
你写成Go的SDK
你可以在Go SDK内部把它这docker-compose给拉起
然后再去跑单元测试
两种我觉得都可以
那么你手动管理也问题不大
那么手动管理可能就要你自己手动去销毁
稍微会麻烦一些
所以我们当时的思路就是说自己写了一个库
我们内部叫lich dangling kink
大家玩过我Sana应该知道
我们自己可以做个Setup Teardown到
Setup其实就是通过docker-compose的命令行把它拉起
然后Teardown把它销毁掉
相对来说比较简单
需要做一些初始化动作
所以我是写的TestMain
那么再去调Run方法的时候才会把你所有的TestXxx方法跑一遍
我们会提前自己去手动去确定要启动和销毁
但这里面稍微有个问题
我觉得不太好的就是你像msql的启动
mysql的启动和销毁真的是有点慢的
可能要十几秒
我还没有想到更好的方法
有可能一个阉割版的mysql快速启动可能会快一些
但的确目前要启动了好几秒钟
这个的确是有点慢
那么其他的层 什么service层 biz这一层
我觉得都可以gomock掉
我们很多都做了面向接口抽象去编程了 对吧
所以你只要有接口你就可以mock掉
所以这个相对来说是比较简单的
那么如果你是在CI环境里面
利用Docker来跑这些依赖的资源
把它拉起在本地拉起
用完之后在销毁掉
其实都是可以的问题不是特别大
CI里面也可以支持docker-compose的
我认为是你的代码提交到远程的gitlab
然后你可以触发一个CI任务
那么这个CI任务可能也会通过docker-compose把msql拉起来
然后会跑一遍
我自己觉得效果也还不错
那么Go官方它是现在提供所谓的tableDriven
我们叫所谓叫子测试对吧
叫Subtests
那么加Gomock
其实是很方便去做完整的一个单元测试的
你比方说我们的api目录
我们api目录有Protobuf的定义
有了Protobuf的定义
你可以生成swagger文件
因为有些情况下还是需要一个swag文件去看看外部UI
那么我们Protobuf文件
因为你知道了API的定义
那么我们其实可以直接
我们基于yapi
就是API的测试框架
怎么讲 就生成yapi需要的输入输出方法名什么的
我们把它生成的定义生成好
导入到yapi系统里面去
所以你的整个这种case这种定义你就不用手填了
会非常方便
因为你基于Protobuf生成好
人家需要的这种JSON文件导进去
或者说你可以到API
把你的API的一些元信息注册到测试框架里面去
所以非常方便
那么data层我刚刚说了
通过docker-compose可以把真实的模拟掉
那么biz层 因为它通常依赖repo或者rpc
都可以通过gomock去模拟掉
业务的单元测试写起来也是非常简单的
那么service它依赖biz的实现
所以可以构建一个真实的biz
因为这个地方我们biz
那个use case是没有具体的一个interstice抽象的
就是我们没有做一个interface抽象
它就是一个具体的实现类
这种情况下
你要把整个biz构建出来
因为biz依赖其它的repo
那么先把repo mock掉
然后启动biz
然后再把service启动起来
你就可以测service层了
在我们内部都是基于git branch来进行feature开发的
我们一般来说都是本地进行单元测试
肯定是你先自己单元测试成功
然后再提交到gitlab的merge request
提到gitlab的merge request的时候
它会自动的进行CI的单元测试
那么CI单元测试通过以后
一般我们会找
基于这个feature branch来构建一个FAT的环境
有了这个FAT的环境
我就可以找小姐姐
利用yapi指向这个
我们可以做什么
多租户染色 对吧
注入到公共测试环境里面去测试
回归一些test case
如果测试通过
我们就会把这个代码合并到我们的主干代码 也叫master
合并到主干以后 我们再在UAT
部署这套主干代码的最新的代码
基于master去部署最新的代码到我们的UAT环境
那么这个时候再在UAT里面进行一个什么 回归
为什么还要进行回归
因为你的主干ABC各种feature都在进
每进一次
最好是什么
全新回归一下
然后之后再交付到我们的线上做预发布 灰度 全量
这个就是我们完整的CI的pipeline
大概是这样的一个流程
其实Go的gotest gomock加上Subtests
这些特性加上我们的一些接口抽象
以及docker-compose的使用
我觉得整个测试体验
其实会我觉得就已经非常好了
以前没有想过
就是说用起msql起redis真的很烦的
现在有了镜像 有了docker image
其实你去搭建一个依赖环境相对非常简单的
你什么都不用管 对吧
直接可以一键启动
所以我认为基本上可以很好的
解决我们单元测试的一个问题
后面整个我们工程化的基本上就讲完了
我们有一个叫beer-shop的一个项目正在开发
我们希望把各种demo都写进去
那么有一些工程代码
大家有一些不清楚
也是可以在我们的课后提出来到底长什么样
我们beer-shop会持续迭代
所以大家会看到一个完整的一个面
当然我们有一些改进
比方说关于Go的error的一些处理
应该是Go的API的error处理
我们可能还会进行一些演进
为什么会做这么多事情
其实就是为了让整个工程化比较标准统一
最终不同的人进来
进来去迭代和开发的时候
大家就受益匪浅
整个就会非常爽
所以我们再切过来看一下我们我当时本地的一个工程
看看其实我们看biz层
biz层的话就定义了domain object
就这么一个对象
定了repo的接口
定义了一个user case
user case其实就是我们的业务服务层 对吧
然后你可以写业务的逻辑 下单
这个就是我们的biz层
这是我本地的
我们的data层其实就是实现了biz接口的一个具体的实现
SaveOrder就存储订单
这个里面我们可能会用facebook的ent orm框架来做持久化
这里面其实就涉及到domain object到po对象的一个转换
其实是这块
那么单元测试我这个地方没有写
所以我不会全部都展示出来
然后还有service层
service层其实因为它最终是
实现了api的interface
所以你看我们这个地方其实就是
实现了api的interface
就是gRPC方法 对吧
所以这个地方其实做什么dto到do的转换
业务领域层也就是bat层的代码
去购买一个下单
然后这边就是构建一个返回
这个其实都是相对来说比较简单的
那么通过这样的一个简单的模板工程
加上我们后续beer-shop的一个持续迭代
希望能给你一个最佳实践
参考我们到底是怎么去做的
包括后面我也会完善很多单元测试在里面
那么这一堂课其实涉及的reference非常多
有DDD的一些设计思想
有DDD和微服务的一些思想
有DDD和中台的一些思想
有目录结构的一些最佳实践
然后也有一些测试方法论的一些最佳实践
还有一些就包含像go mod的一些使用
单元测试的使用
docker-compose也使用
都在里面
所以这些文章大家慢慢去看
还有我们的甚至像依赖注入怎么去使用
所以真的非常多内容的
这块反正课后大家好好吸收一下
因为这一章应该是比较接地气
并且能够帮助你们公司内部的一些Go工程化落地的一个非常好的参考
OK 我们整个工程化的就讲到这里