Skip to content

Cargo workspace与monorepo工程组织

1. 这是什么

当 Rust 项目还很小的时候,一个 crate 往往就够了。
但一旦项目开始变大,很快就会遇到这些情况:

  • 一个仓库里不止一个二进制程序
  • 需要共享公共库代码
  • Web 服务、CLI、工具脚本、测试工具想放在一起维护
  • 团队希望统一依赖、统一版本、统一 CI

这时就会进入一个更工程化的话题:

  • Cargo workspace 与 monorepo 工程组织

一句话理解:

  • workspace 解决的是“多个 Rust crate 怎样作为一个整体协同开发”
  • monorepo 解决的是“多个相关项目怎样在一个仓库里被统一组织和治理”

2. 为什么这件事重要

因为很多项目的复杂度,往往不是先来自语言本身,
而是先来自代码组织方式。

如果组织方式不清楚,常见问题会很快出现:

  • 共享代码被复制粘贴
  • 依赖版本彼此漂移
  • 构建和测试范围越来越难控制
  • 发布某个组件时影响面不清楚
  • 团队不知道什么应该放在公共层,什么应该放在业务层

所以 workspace 不是“仓库稍微大一点时的语法功能”,
而是 Rust 工程化进入中大型项目后的基础设施。

3. 先建立直觉

3.1 crate 是代码单元,workspace 是协作单元

单个 crate 更像是:

  • 一个库
  • 一个可执行程序
  • 一个独立编译与发布单元

而 workspace 更像是:

  • 多个 crate 的协作容器

它让这些 crate 能够:

  • 在同一个锁文件下管理依赖
  • 共享一部分配置
  • 统一构建与测试
  • 形成更清楚的仓库边界

3.2 monorepo 不是“全放一起”,而是“边界清晰地放一起”

很多人第一次理解 monorepo 时,会误以为它只是:

  • 把所有东西塞进一个仓库

但真正有价值的 monorepo 并不是混乱堆叠,
而是:

  • 在一个仓库中维护多个有关联但边界清楚的模块

也就是说,monorepo 的价值不是模糊边界,
而是让依赖关系、共享资产和协同流程更可见。

4. Cargo workspace 真正解决什么问题

4.1 统一依赖与锁文件

当多个 crate 分开维护时,经常会遇到:

  • 同一依赖版本不一致
  • 本地调得通,CI 行为却不稳定
  • 修一个安全漏洞需要逐个更新

workspace 的重要价值之一,就是把多个 crate 放进一个更统一的依赖管理范围。

4.2 共享基础库与基础设施代码

真实项目里通常会有一些公共能力:

  • 配置模型
  • 错误类型
  • 领域模型
  • 数据访问基础层
  • 通用工具函数
  • 协议定义

这些能力如果散落复制,很快就会变成维护灾难。
workspace 让你能把它们更自然地提取成共享 crate。

4.3 统一构建、测试与质量治理

随着 crate 数量增加,团队更关心的通常不是“能不能编译”,
而是:

  • 怎么统一测试
  • 怎么统一 lint
  • 怎么统一格式化
  • 怎么统一依赖策略

workspace 的工程价值,很多时候就体现在这些治理动作能否规模化执行。

5. Rust 项目为什么容易走向 workspace

Rust 生态鼓励明确边界:

  • crate 边界清楚
  • 依赖关系显式
  • 库与二进制程序区分自然
  • 共享逻辑适合被提炼成独立 crate

所以当项目开始成长时,Rust 很自然就会从“单 crate”演化到“多 crate workspace”。
这不是过度设计,而往往是代码体量增长后的自然结果。

6. 什么时候该考虑 monorepo

6.1 当多个组件高度协同时

例如:

  • 一个 API 服务
  • 一个后台任务 worker
  • 一个 CLI 管理工具
  • 一个共享 domain / types crate
  • 一个 SDK 或内部 client

如果这些组件生命周期强相关、经常一起改动,放在一个 monorepo 往往更容易协同。

6.2 当你需要统一治理工程规范时

monorepo 更适合处理:

  • 统一 CI
  • 统一依赖升级
  • 统一发布流程
  • 统一安全扫描
  • 统一代码风格与质量门禁

也就是说,monorepo 的收益不仅在代码复用,
也在流程治理。

7. 真实工程里真正该建立的能力

7.1 区分“共享代码”与“耦合代码”

不是所有重复看起来都应该抽公共 crate。
一个成熟工程要能分辨:

  • 哪些是真正稳定、值得复用的共享能力
  • 哪些只是暂时相似,但抽出来会造成反向耦合

workspace 能帮助拆分,但不会自动帮你做出正确抽象。

7.2 让 crate 边界反映架构边界

如果 crate 的划分只是按文件数量随手切,后续仍会混乱。
更好的思路通常是:

  • 让 crate 边界尽量贴近领域边界、运行单元边界或基础设施边界

例如:

  • app-api
  • app-worker
  • domain
  • infra-db
  • common-config

重点不在名字,而在边界语义是否清晰。

7.3 不要把 workspace 当成“大而全公共桶”

很多团队一旦上 workspace,就容易把各种工具函数都堆进一个 commonutils crate。
这样短期方便,长期却容易变成隐式耦合中心。

所以 workspace 成功与否,不在于 crate 多不多,
而在于:

  • 共享是否克制,边界是否稳定

8. 常见误区

8.1 误区一:项目一开始就必须拆成很多 crate

不一定。
过早拆分会增加结构复杂度。
单 crate 能清楚表达问题时,不必急着多 crate 化。

8.2 误区二:上了 workspace 就等于架构设计好了

不对。
workspace 只是组织手段,不会替你自动消除耦合。

8.3 误区三:monorepo 一定优于多仓库

不一定。
如果组件生命周期差异大、权限边界差异大,monorepo 也可能带来额外治理成本。

8.4 误区四:共享代码越多越好

不对。
共享过度往往会让演化速度下降,并制造隐式依赖。

9. 一个更实用的判断思路

如果你在判断一个 Rust 项目是否该走 workspace / monorepo,可以先问:

  1. 是否已经有多个 crate 或多个运行单元需要协同维护
  2. 是否存在明显的共享基础能力需要稳定复用
  3. 是否需要统一依赖、CI、质量门禁和发布治理
  4. crate 的边界是否能明确反映架构边界,而不是随意切分
  5. 当前问题是结构太散,还是其实单 crate 仍然足够清楚

10. 建议学习顺序

建议按这个顺序理解:

  1. 先区分 crate、package、workspace 各自的角色
  2. 再理解为什么多 crate 会带来依赖与治理问题
  3. 再理解 workspace 怎样帮助统一构建、测试与依赖管理
  4. 再思考共享 crate 的抽象边界应该如何控制
  5. 最后再进入 monorepo 的 CI、发布与权限治理实践

11. 自测标准

  • 能解释单个 crate 与 workspace 的角色差异
  • 能理解 monorepo 的价值不只是“放在同一个仓库”
  • 能知道 workspace 主要解决依赖统一、共享代码与工程治理问题
  • 能意识到 crate 划分应当尽量反映架构边界
  • 能判断一个项目当前是应该继续单 crate,还是值得进入 workspace 组织