Software Architect · 模块 06
当层把重要逻辑保护起来、隔开那些细节时,它才物有所值:HTTP、数据库、队列、SDK,以及当下用的 framework。
Clean Architecture · dependency inversion · ports and adapters
业务规则应该依赖领域的语言,而不是依赖"一个请求是怎么送进来的"。
细节属于外圈
房子的地基不能取决于窗帘的颜色。窗帘你随时能换,地基则几乎换不动。
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 层与外部世界对话。
一个只是把参数从一个方法搬进另一个方法的层,不是架构,是仪式。架构师必须同时防住两头:缺失的边界,以及毫无意义的分层。
可测试性能很快告诉你:依赖到底指对了方向没有。
例子:在 unit test 里不碰数据库就创建一个订单
要验证一份食谱,你不必去开一家餐厅。一间厨房和食材就够了。
CreateOrder 这个 use case 把 InventoryPort、PaymentPort 和 OrderRepository 作为依赖收进来。在 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 的前提下被替换?