返回 2026-07-11
⚙️ 工程

作为组织架构图的包管理Package Management as Org Chart

nesbitt.io·2026-07-10

文章将康威定律应用于依赖管理设计,探讨了软件包管理系统的架构如何反映开发团队的组织结构与沟通模式。作者认为,依赖管理的形态往往是团队组织架构的直接映射。

Andrew Nesbitt

Conway定律指出,组织所产出的系统会复制其自身的沟通结构。依赖管理工具也是系统的一部分。解析策略代表了关于如何解决分歧的意见;清单格式则记录了谁被允许依赖谁。

Monorepo,单一版本策略:依赖树中的每个包必须对每个依赖项的同一个版本达成一致;升级任何内容都意味着同时升级所有人。组织架构图已被禁止出现在代码中。只有当存在一支常设的迁移团队,专门负责拖着所有消费者一起前进时,这种方式才行得通。

带有 workspaces 的 Monorepo:只有一个仓库,但每个包保留自己的清单,并且可以固定自己的版本。一个强势的平台团队,加上认为协调问题早在2019年场外会议上就已解决的管理层。

Git submodules:依赖项通过独立仓库中的确切 commit SHA 进行引用,更新它是一个永远无法自动化的手动两步操作。两个团队同意合作,并用 commit hash 精确记录了他们到底有多敷衍。

Bazel:目标之间的每条依赖边都在 BUILD 文件中显式声明;没有任何隐式或推断的内容。当组织规模扩大到无法通过询问来弄清谁依赖谁时,就会采用它,因此构建系统被用来强制执行人类已经无法追踪的依赖关系。通常只有一个人能看懂,而这个人来自 Google。

Nix / Guix:构建过程是其声明输入的纯函数;在 derivation 中未列出的任何内容在构建时都不存在。“在我的机器上能运行”这句话在结构上变得不可能出现,代价是牺牲了大部分招聘流程。

Maven nearest-wins 调解:当依赖树中的两条路径需要同一个 artifact 的不同版本时,Maven 会选择离根节点跳数较少的那个,而不管哪个版本更新或满足更多约束。通过距离顶部的远近来解决冲突,这也是该组织解决大多数分歧的方式。

在公共 registry 前面部署 Artifactory,私有命名空间中充斥着 fork:每次安装都要经过组织控制的代理,需要修补的包被 fork 进来而不是贡献回上游。信任由系统而非个人授予,而这些 fork 源于法务部赢得的一场争论,此后便再未重启过。

针对内部应用的 deb/rpm 包:应用被构建为 OS 包并由系统包管理器安装,发布过程与内核更新走相同的关卡。运维团队赢了,并从此占据主导。发布成为具有 runbook 的事件,且至少有一人的职位头衔中包含“release”一词。

Docker:应用程序自带一份操作系统副本,因此部署环境完全由开发者在构建时决定。开发团队从运维团队中脱离出来并带走了 OS,运维团队再也无法拒绝包含其曾经控制的一切的 artifact。当下一个 xz 事件发生时,公司才会发现自己到底运行了多少份 Debian。

来自私有 registry 的 Terraform 模块:基础设施被打包为版本化模块,供应用团队像使用其他依赖项一样消费。基础设施团队构建了一个接口,以便应用团队不再寻呼他们,结果现在却因为接口问题而被寻呼。

模块联邦:独立构建和部署的 JavaScript 包在浏览器运行时相互协商共享依赖的版本。各团队向不同的 VP 汇报,而这些 VP 根本不会坐在一起开会,因此版本解析被推迟到了最后关头,也就是用户的机器上,因为在流水线的前期阶段根本无法达成一致。

peerDependencies:包对其宿主必须提供的依赖声明了版本约束,但本身不附带任何满足该约束的内容。这就像框架团队向应用团队下达政策:你们必须使用 React 18,我们会检查,至于如何满足这个约束,那是你们自己的问题。

Vendored 依赖:将每个依赖的源码复制到仓库中并提交;上游的变更或消失都不会产生任何影响。这就像一个曾被上游伤害过、现在开始扣押人质的组织;其代价是永远存在于下一个迭代中的手动合并工作。

没有锁文件,处处皆最新:每次安装都重新向 registry 进行解析,依赖集就是当时最新的版本。创始人仍然直接向 main 分支提交代码,多年前对 npm-shrinkwrap.json 有过糟糕体验,从此再也不碰锁文件。

每次合并都运行 semantic-release:版本号根据 Conventional Commits 前缀计算得出,只要 main 分支发生变动就会自动发布版本。发布成了提交前缀的副作用,因为充当那个发布版本的人已经变成了一种负担。

Go MVS:将每个依赖解析为依赖图中任何模块明确要求的最高版本,且不会更高;不会仅仅因为存在更新版本就进行升级。这是由某个研究过 SAT 求解器解析机制并得出结论认为该问题是人为造成的人所设计的,它体现了一种只有当有人将需求落实到书面时才会发生变更的组织文化。

Brewfile:开发环境被声明为一系列由 brew bundle 按顺序安装的 Homebrew 包。入职就像一种仪式:该文件在第 14 行执行失败,而获得一台可用开发机的实际机制则是一个名为 #dev-setup-help 的 Slack 讨论串。

大多数依赖策略都是为了避免人际协商。工具并不能消除分歧,它只是决定了谁在默认情况下吃亏。

需要完整排版与评论请前往来源站点阅读。