设计模式 E06:从 DIP 到 Ports and Adapters
DIP 说高层策略不应依赖低层细节。Ports and Adapters 把这个方向放到系统边界上:应用层面向端口,数据库、消息、支付、外部服务都是适配器。DDD 战术设计再补一个约束:领域不变量不应丢给适配器或应用脚本去补。
Repository 是端口,不是 GoF 名称复用
labs/E06 的 OrderRepository 只有 save 和 find,InMemoryOrderRepository 是内存适配器。PlaceOrder 应用服务依赖接口,测试把订单 o-1 放进去,再从 repository 找回同一个对象。应用层不知道底层是 Map,也不需要知道未来会不会换成数据库。
1 | |
Repository 不是 GoF 23 种模式之一。Factory 在这里也不是 Abstract Factory 或 Factory Method,而是 DDD 战术里的创建职责:OrderFactory 保证订单创建时已经有第一条订单行。名称相似不能替代意图和调用者边界。
不变量留在领域对象里
Line 拒绝空 SKU 和非正金额;Order.add 在订单批准后拒绝继续添加;Order.approve 要求订单非空,并调用 CreditPolicy 判断授信。仓储只保存和查找,不修正负金额,也不吞掉授信失败。
应用服务负责编排:创建订单、调用领域方法、保存结果。CreditPolicy 在本章只是一个布尔策略端口,用来模拟“授信是否通过”这条可替换规则;它可以演进成领域服务,但当前代码没有证明它必然就是领域服务。聚合和值对象保护内部一致性,Repository port 隔离外部存储。把这些职责都叫“抽象层”会模糊真正的变化方向。
flowchart LR
UseCase[PlaceOrder 应用服务]
Factory[OrderFactory]
Policy[CreditPolicy 策略端口]
Repo[OrderRepository 端口]
Memory[InMemoryOrderRepository 适配器]
Order[Order 聚合]
UseCase -- 运行调用 --> Factory
UseCase -- 运行调用 --> Policy
UseCase -- 运行调用 --> Repo
Factory -- 创建 --> Order
Memory -- 实现端口 --> Repo
Ports and Adapters 保护的是系统边界,DDD 战术对象保护的是领域语义。端口可替换,不代表不变量可以离开领域模型。图里把运行调用和源码实现边分开:应用服务调用端口,内存适配器实现端口。
验证与练习
运行 ./mvnw -B -ntp -pl labs/E06 -am test;PortsAndDomainTest 通过 3 个测试。原始日志在 examples/design-patterns/evidence/E06/targeted.stdout.txt。真实数据库适配器、事务边界、领域事件发布 NOT_RUN。
- 新增
RecordingOrderRepository测试替身,复用同一PlaceOrder测试,证明应用服务只依赖端口。 - 增加“总金额超过 1000 需要人工审核”。先写失败断言,再判断它属于
CreditPolicy、Order还是应用服务。
参考资料:
- Robert C. Martin:The Dependency Inversion Principle。
- Alistair Cockburn:Hexagonal Architecture / Ports and Adapters。
- Eric Evans:Domain-Driven Design Reference: Definitions and Pattern Summaries。
上一节:E05 依赖注入容器、动态代理与 AOP;下一节:E07 真实库里能看到哪些模式。





