写了十多年SOLID原则,用它们构建了InversifyJS,写过洋葱架构的实现,甚至论证过它们是超越面向对象编程的通用设计原则——但作者发现,关于SOLID的讨论中始终缺了一块。
SOLID告诉你如何写出好的组件,却没有告诉你如何把这些组件组合成一个形状仍然可以改变的系统。
一个"完美"的SOLID代码库,拆起来照样要命
想象一个完全符合SOLID的代码库:UserRepository依赖抽象,EmailService职责单一,OrderProcessor对扩展开放、对修改关闭。一切都很干净。
然后CTO说:"我们需要把用户管理拆成独立的微服务。"
接下来就是数周的拆解工作:新建项目、移动文件、重写import、新增入口点、重新配置依赖、把共享接口抽成独立包。SOLID组件本身几乎不需要改动,需要改的是它们周围的结构。系统的边界早已固化在import路径、项目布局、入口点和硬编码的模块装配里。
SOLID给了你优秀的砖块,但如果缺少一条原则来防止这些砖块过早被浇筑成固定形状,你最终还是会得到一个僵化的系统。
多种架构模式,指向同一个隐藏属性
软件工程文献中充斥着各种架构模式:六边形架构、洋葱架构、端口与适配器、组合根、模块化单体、微内核和插件系统。
表面上看它们解决的是不同的问题,但把这些模式运用得当,反复出现的是同一个涌现属性:
你可以改变系统的物理形态,而无需修改执行业务逻辑的代码。
当相互独立的模式收敛到同一个属性时,值得追问:它们是不是某个更深层原则的不同表达?
系统形态指的是物理布局、部署目标和运行时分布。形态决策是组织能做的最昂贵的决策之一:过早选择微服务的团队往往要花数月整合,在单体里停留太久的团队往往要花一年时间拆服务。这些转型之所以痛苦,是因为业务逻辑常常隐含着对运行位置的假设——共享内存、即时响应、单一数据库。
上述模式让系统形态变得可变,让团队能够重新配置物理布局而无需重写领域规则。
组合根:让边界保持可移动的关键
看一个完全通过IoC容器装配的代码库,所有装配逻辑都集中在组合根里:
// monolith/composition-root.tsconst container = new Container();container.load(authModule);container.load(userModule);container.load(orderModule);container.load(emailModule);container.load(cmsModule);这段装配代码就是系统边界的全部所在。想拆微服务?把组合根拆成多个部署单元,每个单元加载自己的模块子集,业务代码一行不用改。
这就是边界独立原则(BIP)的核心:让系统边界成为配置问题,而不是代码问题。边界不应该是被import语句和项目结构焊死的,而应该是可以在组合根里重新排列的。
当边界独立成为设计目标,架构决策就从"一次性赌注"变成了"可逆选择"。这也是为什么那些看似不同的架构模式,最终都指向同一个方向。
热门跟贴