Software Architect · 模块 06

当层把重要逻辑保护起来、隔开那些细节时,它才物有所值:HTTP、数据库、队列、SDK,以及当下用的 framework。

Clean Architecture · dependency inversion · ports and adapters

§ 01

业务规则应该依赖领域的语言,而不是依赖"一个请求是怎么送进来的"。

细节属于外圈

房子的地基不能取决于窗帘的颜色。窗帘你随时能换,地基则几乎换不动。

Clean Architecture、Hexagonal Architecture 和 Onion Architecture 讲的是同一个观点:系统的中心装领域规则和 use case,外层装细节。REST controller、SQL 查询、Redis cache、Stripe SDK、message broker——都是重要的细节,但都不是业务规则的来源。

Dependency Inversion Principle(依赖倒置原则) 让箭头始终指对方向:高层 policy 依赖抽象,实现从外部插进来。落到实践里,它长这样:一个 PaymentGateway port,加上一个 StripePaymentGateway adapter。

一个层必须真的在干活

两个房间之间的空走廊,如果有人穿行,它就有用。如果它只是拉长了距离,那就是浪费的楼面。

当一个层带来隔离、可测试性,或一个独立的变化速率时,它就站得住脚。application 层协调一个 use case。domain 层装规则和 invariant。infrastructure 层与外部世界对话。

一个只是把参数从一个方法搬进另一个方法的层,不是架构,是仪式。架构师必须同时防住两头:缺失的边界,以及毫无意义的分层。

§ 02

可测试性能很快告诉你:依赖到底指对了方向没有。

例子:在 unit test 里不碰数据库就创建一个订单

要验证一份食谱,你不必去开一家餐厅。一间厨房和食材就够了。

CreateOrder 这个 use case 把 InventoryPortPaymentPortOrderRepository 作为依赖收进来。在 unit test 里,它们被换成 fake。测试只验证一条规则:没有固定的价格和一个成功的 payment intent,订单就无法被创建。

数据库和 HTTP 不属于这个测试。这意味着业务规则已经和 infrastructure 分开了。

反例:把 Active Record 当成宇宙的中心

如果所有逻辑都住在工具箱里,那么每次维修都得先翻找那把对的螺丝刀。

ORM model 验证业务规则、发起网络调用、发邮件、写 audit log,还决定权限。在系统还小的时候,这感觉很方便。后来,每个测试都要启动一个数据库,每次改动都拖着一堆 side effect,而 domain 变得无法复用。

问题不在 ORM 本身。问题在于:infrastructure model 最后拥有了业务 policy。

自检
  • 核心规则能不能在没有数据库、没有 HTTP 的情况下被验证? - 哪些依赖向内流,哪些向外流? - framework 在哪里 替你规定了 domain model? - 哪些 adapter 能在不碰 use case 的前提下被替换?