flowchart TB
subgraph S1["外部层"]
E["HTTP / CLI / 队列消费者"]
end
subgraph S2["应用层"]
A["用例编排、事务边界"]
end
subgraph S3["领域层(核心)"]
D["实体 / 值对象 / 领域服务 / 仓储接口"]
end
subgraph S4["基础设施层"]
I["数据库 / 缓存 / 外部 API 实现"]
end
E --> A
A --> D
I -.->|"实现接口"| D
层
只做
绝不做
外部层
协议转换、参数校验、鉴权
业务规则
应用层
编排用例、开事务、发事件
具体算法与 SQL
领域层
业务规则、状态迁移、不变量
依赖框架、读写数据库
基础设施
实现仓储与外部调用
反向调用领域逻辑
判据:把数据库换成另一个存储,领域层代码不用改——依赖方向才是对的。
四、SOLID:五条判据,不是五个名词
原则
判据(怎么检查)
违反时的症状
单一职责 SRP
这个类被修改的理由只有一个吗?
一个类常年被不同需求同时改
开闭 OCP
新增一种类型要不要改老代码?
每加一种支付方式就改 switch
里氏替换 LSP
子类能否完全替代父类?
子类抛父类不会抛的异常
接口隔离 ISP
实现类有没有被迫写空方法?
大接口里一半方法抛「不支持」
依赖倒置 DIP
高层是否依赖抽象?
new 具体实现、难以替换测试
// ❌ 一个类承担三件事,任何一件变化都会改它class OrderService { public function create(array $data) { /* ... */ } public function sendEmail() { /* ... */ } public function calculateTax() { /* ... */ }}// ✅ 按变化原因拆分,各自独立演进class OrderService { public function create(array $data): Order { /* ... */ } }class OrderNotifier { public function notify(Order $order): void { /* ... */ } }class TaxCalculator { public function calculate(Order $order): float { /* ... */ } }
五、模块边界:高内聚低耦合的可判定标准
检查项
合格
不合格
跨模块调用
只通过公开接口/事件
直接查对方数据库表
数据归属
每张表只有一个写入方
两个服务都能写同一张表
依赖数量
单个模块依赖 ≤5 个其他模块
依赖成网状,改一处动全身
循环依赖
无
A 调 B,B 又调 A
变更影响
一次典型需求只改 1–2 个模块
改 3 个以上模块
核心规律:耦合不是「有没有调用」,而是「对方变了你要不要跟着变」。共享数据库是最强的耦合。
六、ADR:把取舍写下来,比画图重要
# ADR-007 订单与库存的边界背景:大促期间库存扣减与订单创建互相阻塞选项:A 同一事务(简单但锁竞争) B 预占+异步确认(复杂但解耦) C 上分布式事务(重)结论:选 B,预占库存 + 超时补偿代价:需处理补偿失败与对账失效条件:日订单量 < 1 万 或 库存服务不可独立部署时,退回 A