1340 字
7 分钟
DDD 落地实践:分层架构与建模示例

前几篇讲完了概念,这篇用一个真实的例子——图书借阅,把 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 想达到的效果。

落地时的常见误区#

最后,整理几个我踩过/看到过的坑:

  1. 不要为了 DDD 而 DDD:业务简单时强行分层只会增加复杂度。DDD 的价值在复杂业务。
  2. 不要把领域层写脏:最常见的失败是领域层里出现了 @EntityRepository 的实现、甚至 SQL,导致领域层被技术绑架。
  3. 应用层要”薄”:应用层只做编排和事务,真正的业务规则要下沉到领域层。应用层胖了,就是”贫血模型”回潮。
  4. 从小处开始:先在一个上下文试点,别指望一步到位。

总结#

这一系列四篇笔记,完整走了一遍 DDD:

  1. 认识 DDD:以业务领域为核心的方法论。
  2. 战略设计:子域划分、限界上下文、统一语言。
  3. 战术设计:实体、值对象、聚合、仓储、领域服务。
  4. 落地实践:分层架构 + 一个完整建模示例。

如果你和我一样,正在用 DDD 的项目里摸索,建议从战略设计的限界上下文战术设计的聚合这两个概念抓起——它们是最能体现 DDD 价值、也最容易被忽视的地方。

祝我们都能在 DDD 的路上少踩点坑。😄

DDD 落地实践:分层架构与建模示例
https://www.bczai.xyz/posts/ddd-in-practice/
作者
边城仔
发布于
2026-08-26
许可协议
CC BY-NC-SA 4.0