什么是单体仓库,为什么在 Go 中需要它?
单体仓库(monorepo)是一种代码管理方法,将所有项目、库和服务存储在一个仓库中。在 Go 生态系统中,由于对模块和 workspace 的内置支持,这种方法变得特别流行。
对于初学者开发者来说,单体仓库提供了几个关键优势:
- 统一的代码版本 — 所有依赖项保持同步
- 简化的重构 — 更改在所有项目中立即可见
- 标准化 — 相同的 linter、CI/CD 和测试配置
- 透明性 — 易于理解系统不同部分之间的关联
在本文中,我们将探讨如何正确组织 Go 单体仓库,从基本结构开始,到实际示例结束。
Go 单体仓库的基本结构
正确的代码组织是成功单体仓库的基础。让我们看看典型的结构:
my-monorepo/├── apps/│ ├── web-api/ # 主应用程序│ │ ├── main.go│ │ ├── go.mod│ │ └── internal/│ └── worker/ # 后台处理程序│ ├── main.go│ └── go.mod├── libs/│ ├── config/ # 通用配置│ │ ├── config.go│ │ └── go.mod│ └── logger/ # 日志记录│ ├── logger.go│ └── go.mod├── scripts/ # 辅助脚本├── docs/ # 文档├── go.work # Go workspace└── README.md此结构的关键原则:
- apps/ — 可执行应用程序(二进制文件)
- libs/ — 可复用的库
- scripts/ — 自动化和工具
- docs/ — 项目文档
为单体仓库配置 Go Workspace
从 Go 1.18 开始,通过 go.work 文件提供了对 workspace 的官方支持。这使得可以同时处理多个模块,而无需将它们发布到远程仓库。
为我们的单体仓库创建一个 workspace:
// go.workgo 1.22
use ( ./apps/web-api ./apps/worker ./libs/config ./libs/logger)创建 go.work 后,use 列表中的所有模块都可以在本地相互访问。这在开发时特别方便——库中的更改会立即在应用程序中体现。
初始化模块
每个组件都需要创建自己的 go.mod。以配置库为例:
// libs/config/go.modmodule github.com/your-org/libs/config
go 1.22以及 web-api 应用程序:
// apps/web-api/go.modmodule github.com/your-org/apps/web-api
go 1.22
require github.com/your-org/libs/config v0.0.0
replace github.com/your-org/libs/config => ../../libs/config请注意 replace 指令——它告诉 Go 使用库的本地版本,而不是从互联网上查找。
代码示例:库和应用程序
创建一个简单的配置库和一个使用它的应用程序。
库 libs/config
// libs/config/config.gopackage config
import ( "fmt" "os")
type Config struct { Port string Env string}
func Load() (*Config, error) { port := os.Getenv("PORT") if port == "" { port = "8080" }
env := os.Getenv("ENV") if env == "" { env = "development" }
return &Config{ Port: port, Env: env, }, nil}
func (c *Config) String() string { return fmt.Sprintf("Config{