十多年来,我一直在撰写关于 SOLID 原则的文章。正是因为这些原则,我开发了 InversifyJS;我写过如何结合洋葱架构来实现这些原则;就在最近,我还提出它们是普适的设计原则,其影响远远超出了面向对象编程的范畴。但我始终觉得有些东西缺失了——不是原则本身的问题,而是围绕这些原则的讨论中缺少了某些关键内容。
SOLID 告诉你如何编写良好的组件,但它没有告诉你如何将这些组件组合成一个能够灵活变形的系统。
让我来解释一下。
缺失的一环
假设你拥有一个完全符合 SOLID 原则的代码库。UserRepository 依赖于某个抽象接口,EmailService 职责单一,OrderProcessor 对扩展开放、对修改关闭。一切都非常完美,你严格遵循了这五项原则。
这时,你的首席技术官走进来说:“我们需要把用户管理拆分成一个独立的微服务。”
接下来会发生什么?你开始大刀阔斧地重构:创建新项目、移动文件、重写导入语句、新增入口点、重新配置依赖关系,并将共享的接口提取到一个独立的包中。整个过程可能耗时数周。实际上,那些 SOLID 组件本身并没有问题——它们根本不需要改动。真正需要改变的是它们周围的结构。系统的边界早已被硬编码进源代码中:体现在导入路径里、入口点里、哪些文件属于哪个项目,以及模块内部硬编码的依赖装配逻辑中。
SOLID 为你提供了精致的组件,但如果这些精致的组件被束缚在一个僵化的结构中,整个系统依然会是僵化的。
引入组合根之后的变化
现在,想象同样的代码库,但这一次所有组件都通过一个控制反转(IoC)容器进行装配,且所有的装配逻辑都集中在一个地方:组合根(Composition Root)。
你的 OrderProcessor 不再直接导入 UserRepository,而是声明对某个抽象接口的依赖。组合根正是抽象接口与具体实现相遇的地方。在整个代码库中,只有组合根知道哪些具体类存在,以及它们之间如何关联。
有趣的地方就在这里。在单体应用中,你只有一个组合根,它负责加载所有模块:
// monolith/composition-root.ts
const container = new Container();
container.load(authModule);
container.load(userModule);
container.load(orderModule);
container.load(emailModule);
container.load(cmsModule);
现在,你的首席技术官想要将系统拆分……
免责声明:本文内容来自互联网,该文观点不代表本站观点。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,请到页面底部单击反馈,一经查实,本站将立刻删除。