知识卡片

服务边界不是架构边界,SOLID组件设计才能真正化解横跨型变更

结构图卡

内容

[[运送猫咪的难题横跨型变更让按功能切分的微服务全体强耦合]]暴露的问题,靠”对象化”来解决:用SOLID原则预先设计出一系列多态类来应对未来的功能扩展——原先服务化设计里的大部分逻辑被收进对象模型的基类,每次特定行程的逻辑抽成独立的Rides组件,运送猫咪这个新功能则装进Kittens组件,两者都用模板方法或策略模式覆盖抽象基类,并且都遵守依赖关系规则(实现类由UI控制下的工厂类创建)。在这套设计里,引入运送猫咪功能只需要变更TaxiUI组件、外加部署一个新的jar/Gem/DLL文件,系统运行时会自动动态加载,其余组件完全不用动——猫咪功能就这样和系统其余部分实现了真正的解耦,能独立开发部署。这个思路同样能用在服务本身:服务不必是小型单体程序,一个Java服务可以是一个或多个jar文件里的一组抽象类,每次新功能扩展是另一个jar文件里扩展了这些抽象类的衍生类——部署新功能因此不再是”部署一个服务”,而只是往服务的加载路径下加一个jar文件,这个过程恰好符合开闭原则。这样服务的边界外观没变,但内部长出了组件结构,靠衍生类给自己添加新功能。由此得出本章的核心结论:系统的架构边界事实上并不落在服务之间,而是穿透所有服务、以组件的形式存在于服务内部——要真正化解横跨型变更问题,必须在服务内部采用遵守依赖关系原则的组件设计。服务化可能有助于提升可扩展性和研发效率,但服务本身并不代表整个系统的架构设计:架构是由架构边界及边界间的依赖关系定义的,与组件之间是靠调用还是靠网络通信无关——一个服务可能本身就是一个独立组件(以架构边界形式隔开),也可能由多个内部组件构成、彼此仍以架构边界隔离,极端情况下客户端和服务端甚至可能耦合得过紧、根本不具备架构意义上的隔离性。

结构图

flowchart TD
    subgraph 错误认知: 服务边界即架构边界
    S1[TaxiUI服务] -.按功能切分,新功能横跨全部服务.-> S2[TaxiFinder服务]
    S2 --> S3[TaxiSelector服务]
    S3 --> S4[TaxiDispatcher服务]
    end
    subgraph 正确认知: 架构边界在服务内部的组件里
    ServiceX[任意一个服务] --> Base[抽象基类, 遵守依赖关系规则]
    Base -.衍生类扩展,符合OCP.-> Rides[Rides组件]
    Base -.衍生类扩展,符合OCP.-> Kittens[Kittens组件: 新功能只需加一个jar]
    end

参考来源

- 位置:《架构整洁之道》第27章《服务:宏观与微观》"对象化是救星""基于组件的服务""横跨型变更""本章小结"(源文件:_epub-src/text/part0014_split_012.html) - 结论依据:原文说明用SOLID设计的多态类(Rides/Kittens组件覆盖抽象基类)让新功能只需新增一个组件文件而不影响其他组件,并将同样思路用于服务内部组件化,最终得出"系统架构边界穿透所有服务、以组件形式存在于服务内部"的核心结论,直接支撑本卡片的结构图与解释。 - 原始内容:如果我们在这种架构下引入运送猫咪的功能,TaxiUI组件就必须随之变更,但其他的组件就无须变更了……系统的架构边界事实上并不落在服务之间,而是穿透所有服务,在服务内部以组件的形式存在……服务边界并不能代表系统的架构边界,服务内部的组件边界才是。