在golang世界中,复杂的包依赖是一个比较大的问题。明明代码中只引用了某个库的一些函数,但依赖库可能会一大堆。依赖库越多,最后编译生成的二进制文件越大,怎么能在保证程序运行的时候,设计一个结构良好,尽量减少依赖的库呢?这个时候monorepo成了可行的解决方案,那么什么是monorepo?
Monorepo是将多个相关的项目、模块(如前端应用、后端服务、组件库、文档)放在同一个 Git 仓库中管理。这与传统的 MultiRepo(每个项目独立一个仓库)形成对比。有人这个时候可能会问为什么不用submodule呢?其实它们代表了两种不同的哲学:一个是“集中式”的协作,另一个是“引用式”的集成。
| 特性维度 | Monorepo (单体仓库) | Git Submodule (子模块) |
|---|---|---|
| 核心概念 | 所有项目代码物理上存放在同一个仓库中 | 主仓库中只保存子仓库的链接和特定提交索引,代码独立存储 |
| 仓库结构 | 单一巨型仓库,项目通常放在 packages/, apps/等目录下 | 主仓库 + 多个子仓库的伞形结构 |
| 代码管理 | 统一版本历史,所有代码变更在同一仓库内进行 | 每个子模块有自己独立的版本历史,需分别提交和推送 |
| 依赖管理 | 依赖可提升至根目录,减少冗余;使用单一锁文件 | 每个子模块维护自己独立的依赖,可能冗余 |
| 工作流程 | 原子提交:一次提交可跨多个项目,保证一致性 | 流程复杂:需先在子模块提交,再回主仓库更新子模块指针 |
| 权限控制 | 粒度较粗,控制复杂 | 可为不同子模块设置不同访问权限,控制更精细 |
这里选择monorepo可以简化流程,我们可以设计一个结构良好的库,库中的每个模块都是松耦合的,有自己单独的go module(go.mod,go.sum),代码里面如果只引用了某个模块,那么就只会引入某个模块的依赖,而不是整个库的依赖,这样可以大大减少依赖库的数量,从而减小最后编译成的二进制文件大小。
例如库:github.com/hdget/utils,它下面有很多子工具类,不同的子工具类可能会有不同的依赖,我们采用monorepo的设计,那么只有引用的子工具的依赖才会引入
github.com/hdget/utils
|-- ast
|-- go.mod
|-- go.sum
|-- ast.go
|-- reflect
|-- go.mod
|-- go.sum
|-- reflect.go
那么这个时候问题来了,当我们引用`import githuhb.com/hdget/utils/ast"的时候,这个版本怎么确定呢?
这就是我们要重点介绍的地方,怎么给monorepo下的子库打上版本号
首先进入monorepo的根目录, 给对应子目录的子库打上tag, 最后推送到远程,例如:
cd /path/to/utils
git tag -a "ast/v0.0.1" -m "add version"
git push origin ast/v0.0.1
这样就成功给子目录下的子库打上了版本号。