前几篇讲完了概念,这篇用一个真实的例子——图书借阅,把 DDD 从战略设计到战术设计、再到代码分层完整串一遍。理解了这一个例子,你就掌握了 DDD 落地的主线。
例子的背景
假设我们要做一个图书馆借阅系统。为了简化,它涉及这样几个业务对象:读者、图书、借阅记录。
如果沿用传统的三层架构,很可能会写成这样:
Controller → Service → DAO(直接用数据库表)数据库里可能直接有 book 表(含库存)、reader 表、borrow_record 表。业务逻辑(比如”这本书还能不能借”)被散落在 Service 的多个方法里,或者干脆写在 SQL 里。等业务复杂了,就难以维护。
下面我们看看用 DDD 怎么重新组织。
第一步:战略设计——划定边界
对于这个小系统,我们可以先做战略设计的思考:
- 核心子域:借阅管理。这是系统最核心的价值。
- 支撑子域:图书管理(藏书、库存),支撑借阅。
- 通用子域:读者账号、权限,用现成方案即可。
然后划出限界上下文,至少分成两个:
- 图书上下文:管理图书信息、库存、上架下架。
- 借阅上下文:管理借阅流程、逾期、归还。
你会发现,“图书”在两个上下文里的含义不同:图书上下文里图书有”库存”,借阅上下文里图书只是”可以被借的东西”。这正是限界上下文存在的意义。
第二步:战术设计——借阅上下文内部建模
聚焦借阅上下文,我们来找实体、值对象和聚合。
实体(有身份、会变化):
Borrower(借阅者)—— 有 ID。Borrow(借阅记录)—— 有 ID,状态会从”借出”变到”归还”。
值对象(无身份、不可变):
BookId(图书 ID)—— 只是对一本具体书的引用。DueDate(应还日期)—— 一个日期值,没有身份。BorrowPeriod(借阅周期)—— 包含开始与应还日期。
聚合 + 聚合根:
借阅记录 Borrow 可以作为一个聚合根,它包含 Borrower 和几条 BorrowItem(借了哪几本书),对外只允许通过 Borrow 来操作。这样”一次借阅”这个业务动作的一致性就有保证了。
领域服务:
“借书”这个动作会同时判断图书库存、更新借阅记录,跨越了图书上下文和借阅上下文,所以可以放到领域服务 BorrowingService 里。
第三步:分层架构
DDD 常用的分层是 四层架构,从外到内:
展现层(Controller / 前端 API) ↓应用层(Application Service:编排用例、事务) ↓领域层(Domain:实体、值对象、聚合、领域服务、仓储接口)★核心 ↓基础设施层(Infrastructure:仓储实现、数据库、外部调用)关键在依赖方向:依赖永远指向领域层。也就是说:
- 领域层不依赖任何技术细节(不 import 数据库、不依赖框架);
- 应用层依赖领域层的接口;
- 基础设施层实现领域层定义的仓储接口。
这样,领域层(也就是业务本身)就变得纯粹、可测试、不受技术绑架。
第四步:看看代码大致长什么样
领域层 —— 值对象 BookId:
public record BookId(String value) {}领域层 —— 聚合根 Borrow:
public class Borrow { private final BorrowId id; private final BorrowerId borrowerId; private final List<BorrowItem> items; private BorrowStatus status;
// 只有聚合根能改变自己的状态 public void returnBooks() { if (this.status == BorrowStatus.RETURNED) { throw new IllegalStateException("already returned"); } this.status = BorrowStatus.RETURNED; }}领域层 —— 仓储接口:
public interface BorrowRepository { Borrow findById(BorrowId id); void save(Borrow borrow);}基础设施层 —— 仓储实现:
public class JpaBorrowRepository implements BorrowRepository { @Override public Borrow findById(BorrowId id) { // 用 JPA / MyBatis 等具体技术实现 return ...; }}可以看到,领域层代码里没有任何数据库或框架的影子,它只表达业务。这正是 DDD 想达到的效果。
落地时的常见误区
最后,整理几个我踩过/看到过的坑:
- 不要为了 DDD 而 DDD:业务简单时强行分层只会增加复杂度。DDD 的价值在复杂业务。
- 不要把领域层写脏:最常见的失败是领域层里出现了
@Entity、Repository的实现、甚至 SQL,导致领域层被技术绑架。 - 应用层要”薄”:应用层只做编排和事务,真正的业务规则要下沉到领域层。应用层胖了,就是”贫血模型”回潮。
- 从小处开始:先在一个上下文试点,别指望一步到位。
总结
这一系列四篇笔记,完整走了一遍 DDD:
- 认识 DDD:以业务领域为核心的方法论。
- 战略设计:子域划分、限界上下文、统一语言。
- 战术设计:实体、值对象、聚合、仓储、领域服务。
- 落地实践:分层架构 + 一个完整建模示例。
如果你和我一样,正在用 DDD 的项目里摸索,建议从战略设计的限界上下文和战术设计的聚合这两个概念抓起——它们是最能体现 DDD 价值、也最容易被忽视的地方。
祝我们都能在 DDD 的路上少踩点坑。😄