title: DDD 核心概念梳理:从领域模型到领域事件
date: 2026-07-09
tags: DDD, 架构设计
excerpt: 系统梳理领域模型、统一语言、限界上下文、聚合、领域服务、应用服务、基础设施和领域事件等 DDD 核心概念,帮助理解领域驱动设计的基本建模思路。
draft: false
DDD 核心概念梳理:从领域模型到领域事件
领域驱动设计(Domain-Driven Design,DDD)强调以业务领域为中心组织软件模型。它并不是简单地把数据库表映射成对象,而是希望通过代码准确表达业务概念、业务规则和业务流程。
在复杂业务系统中,DDD 的价值主要体现在三个方面:
- 让代码结构更贴近真实业务;
- 降低业务人员与开发人员之间的沟通成本;
- 将核心业务规则沉淀到稳定、清晰的领域模型中。
本文围绕 DDD 中几个常见核心概念进行梳理,包括领域模型、统一语言、限界上下文、聚合、领域服务、应用服务、基础设施和领域事件。
1. 领域模型:业务规则的代码化表达
领域模型是 DDD 的核心。它用于描述某个业务领域中的核心对象、状态变化、业务规则和对象之间的关系。
需要注意的是,领域模型并不等同于数据表模型。数据库表更关注数据如何存储,而领域模型更关注业务规则如何表达。
以订单系统为例,订单模型通常不只是一个 Order 数据表,而是包含:
- 订单基本信息;
- 订单明细;
- 收货地址;
- 支付状态;
- 发货状态;
- 取消规则;
- 退款规则;
- 金额计算规则。
这些对象和规则共同构成了订单领域模型。
一个好的领域模型应该能够回答:
- 业务中有哪些核心对象?
- 这些对象之间有什么关系?
- 哪些规则必须被严格保证?
- 对象状态在什么条件下可以变化?
2. 统一语言:让业务表达和代码表达保持一致
统一语言是团队在需求讨论、文档编写、代码命名和接口设计中共同使用的一套业务语言。
它的目标是减少沟通歧义,让业务人员和开发人员在同一套概念体系下协作。
例如,在优惠券系统中,如果业务上统一称为"优惠券",代码中就不应同时出现多种含义接近但边界不清的命名:
java
Coupon
Voucher
DiscountTicket
PromotionCard
如果这些词表示的是同一个业务概念,就应该统一命名;如果它们表示不同概念,则需要明确各自的业务边界。
统一语言的核心原则是:
业务中怎么表达,代码中就尽量怎么表达。
这样可以让代码本身具备更强的业务可读性。
3. 限界上下文:明确模型的适用边界
限界上下文用于划定一个模型生效的业务边界。在这个边界内部,概念含义保持一致;离开这个边界后,同一个词可能具有不同含义。
大型系统中,一个概念往往会被多个业务模块使用,但不同模块关注的角度并不相同。
以"用户"为例:
| 上下文 | 关注点 |
|---|---|
| 账号上下文 | 登录账号、密码、手机号、权限 |
| 订单上下文 | 下单人、收货地址、支付信息 |
| 营销上下文 | 用户标签、消费等级、优惠券偏好 |
| 客服上下文 | 投诉记录、售后历史、服务等级 |
虽然这些模块都可能使用"用户"这个词,但它们关心的内容不同,因此不一定适合共用同一个 User 模型。
限界上下文的意义在于:
不要试图用一个大而全的模型覆盖所有业务场景,而应该在清晰边界内建立适合当前业务的模型。
4. 聚合:维护一组对象的一致性
聚合是一组业务上强相关的对象集合。它们通常需要作为一个整体维护业务一致性。
聚合不是简单的对象组合,而是用于回答一个更关键的问题:
哪些对象必须一起修改,哪些规则必须一起保证?
以订单为例,一个订单聚合可能包含:
text
Order
├── OrderItem
├── Address
└── PaymentInfo
订单金额、订单明细、优惠信息和运费之间存在强关联。如果外部系统可以随意修改订单明细,就可能导致订单金额、支付状态和实际商品信息不一致。
因此,这些对象需要被组织在同一个聚合中,由统一入口控制修改行为。
5. 聚合根:聚合对外的唯一入口
每个聚合都应该有一个聚合根。聚合根是外部访问聚合内部对象的唯一入口。
仍以订单聚合为例,Order 通常就是聚合根。外部代码不应直接修改 OrderItem,而应通过 Order 提供的方法完成操作:
java
order.changeItemQuantity(itemId, quantity);
这样 Order 就可以在修改前统一检查业务规则,例如:
- 订单是否允许修改;
- 商品数量是否合法;
- 订单金额是否需要重新计算;
- 当前订单状态是否支持变更。
相比之下,如果外部直接操作订单明细:
java
orderItem.setQuantity(10);
就可能绕过订单规则,破坏聚合内部一致性。
聚合根的核心作用是:
控制聚合内部对象的变更,保证业务规则不会被绕过。
6. 领域服务:承载跨对象的领域行为
有些业务行为明显属于领域逻辑,但并不适合放到某个实体或值对象中。此时可以使用领域服务。
领域服务不是普通工具类,它应该表达明确的业务动作或业务规则。
例如转账行为涉及两个账户:
text
A 账户扣款
B 账户入账
检查账户状态
检查余额
记录交易流水
这个行为不完全属于 A 账户,也不完全属于 B 账户,因此可以抽象为:
java
TransferDomainService
领域服务适合处理:
- 跨多个实体的业务操作;
- 不自然属于单个对象的业务规则;
- 具有明确领域含义的计算或判断逻辑。
7. 应用服务:编排业务用例的执行流程
应用服务负责组织一次完整的业务用例。它主要关注流程编排,而不是承载核心业务规则。
以创建订单为例,应用服务可能负责:
text
1. 接收创建订单请求
2. 查询用户和商品信息
3. 创建订单聚合
4. 调用领域逻辑计算金额
5. 保存订单
6. 发布订单创建事件
其中,"订单是否允许创建""订单金额如何计算""订单状态如何变化"等规则,应尽量放在领域模型或领域服务中,而不是堆在应用服务中。
应用服务更像是业务流程的协调者,它负责把领域对象、仓储、消息、事务等组件组织起来,完成一个用例。
8. 基础设施:提供技术实现能力
基础设施层负责支撑系统运行所需的技术能力,包括:
- 数据库读写;
- Redis 缓存;
- 消息队列;
- 文件存储;
- 第三方接口调用;
- ORM 框架;
- 系统配置。
例如,订单保存可以通过仓储接口表达:
java
OrderRepository
而具体实现可以放在基础设施层:
java
OrderRepositoryImpl
其内部可以使用 MyBatis、JPA、Redis 或其他技术组件。
领域模型不应该关心数据最终存储在 MySQL、PostgreSQL 还是 MongoDB 中。它只需要表达业务概念和业务规则,具体技术细节应由基础设施层处理。
9. 领域事件:表达已经发生的业务事实
领域事件用于描述领域中已经发生的、具有业务意义的事实。
领域事件通常使用过去式命名,例如:
text
OrderCreated
PaymentCompleted
CouponIssued
UserRegistered
以订单支付成功为例,支付完成后系统可能需要执行多个后续动作:
- 修改订单状态;
- 通知仓库发货;
- 增加用户积分;
- 发送通知消息;
- 生成账单流水。
如果这些逻辑全部写在支付流程中,代码会变得臃肿且耦合严重。更合理的方式是发布一个领域事件:
java
PaymentCompletedEvent
然后由不同模块监听该事件,各自完成后续处理。
领域事件的价值在于:
- 表达业务中已经发生的重要事实;
- 降低模块之间的直接依赖;
- 支持业务流程的异步扩展;
- 让核心业务动作和后续响应逻辑解耦。
10. 应用服务与领域服务的区别
应用服务和领域服务容易混淆,可以从职责上区分。
| 对比项 | 应用服务 | 领域服务 |
|---|---|---|
| 关注点 | 用例流程编排 | 领域规则和业务行为 |
| 是否包含核心业务规则 | 不应包含 | 可以包含 |
| 典型职责 | 调用领域对象、保存数据、发布事件 | 处理跨对象业务逻辑 |
| 命名示例 | OrderApplicationService | PricingDomainService |
| 常见场景 | 创建订单、提交支付、发起退款 | 价格计算、库存分配、账户转账 |
简单来说:
应用服务负责"流程怎么走",领域服务负责"业务规则怎么判断、怎么算"。
11. 各概念之间的关系
可以用下面的结构理解 DDD 中这些概念的关系:
text
限界上下文:划定业务边界
↓
统一语言:统一边界内的概念表达
↓
领域模型:用代码表达业务对象和规则
↓
实体 / 值对象 / 聚合:组织领域模型
↓
聚合根:控制聚合内部一致性
↓
领域服务:处理跨对象的业务行为
↓
应用服务:编排业务用例流程
↓
基础设施:提供数据库、缓存、消息等技术能力
↓
领域事件:通知业务中已经发生的重要事实
这些概念不是孤立存在的,而是共同服务于一个目标:
让复杂业务系统的代码结构更加清晰、稳定,并且能够随着业务变化持续演进。
12. 示例:用户下单流程中的 DDD 概念
以"用户下单"为例,可以将 DDD 概念对应到具体业务中:
| DDD 概念 | 示例 |
|---|---|
| 限界上下文 | 订单上下文 |
| 统一语言 | 订单、订单明细、收货地址、支付状态 |
| 领域模型 | Order、OrderItem、Address、PaymentStatus |
| 实体 | Order,具有唯一订单号 |
| 值对象 | Address,只描述地址信息 |
| 聚合 | Order 聚合包含 OrderItem 和 Address |
| 聚合根 | Order |
| 领域服务 | PriceCalculateService,处理复杂价格计算 |
| 应用服务 | OrderApplicationService,编排创建订单流程 |
| 基础设施 | OrderRepositoryImpl,负责订单持久化 |
| 领域事件 | OrderCreatedEvent,表示订单创建成功 |
通过这个例子可以看到,DDD 并不是为了增加概念复杂度,而是为了让业务结构在代码中有更清晰的表达。