← [ BACK_TO_NOTES ]

Domain-Driven Design(DDD)

DDD 是处理复杂业务领域的建模与设计方法:通过 Ubiquitous Language、Subdomain、Bounded Context 建立战略边界,再在上下文内部用 Entity、Value Object、Aggregate、Domain Service、Domain Event 等战术模式表达业务规则。

#pattern #concept #evergreen

[!info] related notes

Domain-Driven Design(DDD)

一句话定义

DDD 的核心不是“使用很多设计模式让代码更优雅”。

更准确地说:

DDD 用业务语言和领域模型控制复杂业务知识,使软件边界与业务边界尽量一致。

它最适合:

  • 业务规则复杂;
  • 术语容易产生歧义;
  • 多团队协作;
  • 规则长期演化;
  • 数据模型不能只靠 CRUD 表结构表达;

的系统。

DDD 先解决什么问题

一个复杂系统常出现:

业务说“订单已确认”
开发说 status = 2
财务说“确认”代表已记账
仓储说“确认”代表可出库

这时问题不是缺一个 Repository pattern。

真正问题是:

  • 语言不统一;
  • 边界不清楚;
  • 同一个词在不同上下文含义不同;
  • 代码结构无法表达业务 invariant。

DDD 首先解决这些建模问题。

Strategic DDD

Strategic DDD 负责:

系统应该按哪些业务边界拆开,每个边界里语言和模型是什么。

Ubiquitous Language

开发者与领域专家在一个上下文中使用同一套业务语言。

例如:

Order.confirm()
Order.cancel()
Payment.authorize()

比:

updateStatus(2)
processType(7)

更接近领域含义。

详见 Ubiquitous Language。

Subdomain

大的业务 Domain 可以拆成多个 subdomain。

常见分类思路:

  • Core Domain;
  • Supporting Subdomain;
  • Generic Subdomain。

核心不是标签本身,而是:

哪些业务能力真正形成不同的问题空间和竞争价值。

Bounded Context

Bounded Context 定义:

某一个领域模型和语言在哪个边界内有效。

例如同一个 “Customer”:

Sales Context
→ lead / buyer / account

Billing Context
→ payer / invoice owner

Support Context
→ ticket requester

不要求整个系统只有一个万能 Customer model。

详见 DDD 限界上下文。

Bounded Context 不等于 Microservice

这是常见误区。

Bounded Context 是:

模型与语言的业务边界。

Microservice 是:

一个 deployment/runtime/service boundary。

两者经常有关联,但不是数学等号。

一个 bounded context 可以:

  • 位于 modular monolith;
  • 最终成为一个 service;
  • 在合理情况下由多个 deployment component 支撑。

不要为了“做 DDD”强制拆微服务。

Tactical DDD

Tactical DDD 负责:

在某个 Bounded Context 内,怎样把业务规则表达成模型。

Entity

Entity 的关键是 identity continuity。

两个对象即使属性相同,只要业务上需要区分“是谁”,就是不同 entity。

例如:

Order #A123
Order #B456

即使金额完全相同,也不是同一个订单。

Entity 不应该只是数据库字段容器。

它应该保护自己的业务规则和有效状态变化。

Value Object

Value Object 没有业务 identity,主要由值定义。

例如:

  • Money;
  • EmailAddress;
  • DateRange;
  • Coordinates。

常见特征:

  • immutable;
  • equality based on value;
  • 可以封装 validation 和 domain operation。

如果两个 Money 都是:

100 CNY

在大多数业务语境里可以互换。

Aggregate

Aggregate 是:

必须在一个 consistency / transaction boundary 内保持 invariant 的对象集合。

其中有一个 Aggregate Root。

外部通常通过 root 修改 aggregate 内部状态。

例如:

Order Aggregate
├─ Order (root)
└─ OrderItem[]

规则可能是:

  • OrderItem 不能绕过 Order 自己改变订单总价;
  • confirm 前必须至少有一个 item;
  • cancelled order 不能继续新增 item。

Aggregate 不是“把有关联的表都塞一起”

Aggregate 的边界应该由:

哪些业务 invariant 必须原子保持一致

决定。

不是由 ER foreign key 数量决定。

小 Aggregate

过大的 aggregate 会带来:

  • 更大的 transaction;
  • 更多 lock/contention;
  • 更高并发冲突;
  • 不必要的对象加载;
  • 更难独立演化。

常见原则:

只把真正需要强一致的对象放进同一个 aggregate。

跨 aggregate 通常通过 ID 引用,并接受 eventual consistency。

Domain Service

如果一条业务规则:

  • 是真正的 domain logic;
  • 但不自然属于某个 Entity / Value Object;

可以放 Domain Service。

例如:

DeliveryScheduler.schedule(...)

可能需要多个 domain object 才能决定结果。

详见 领域服务 vs 应用服务。

Application Service

Application Service 负责 orchestrate use case:

authenticate principal
→ load aggregate
→ call domain behavior
→ persist
→ publish event
→ return DTO

它通常不应该包含核心业务 invariant。

所以:

Application Service
= workflow / orchestration

Domain Model
= business rules

Domain Event

Domain Event 表达:

领域中已经发生的一件有业务意义的事。

例如:

  • OrderConfirmed;
  • PaymentAuthorized;
  • DeliveryCancelled。

不要把:

row inserted
record updated

机械称为 domain event。

Domain Event 讲的是业务事实,而不是数据库操作事实。

Domain Event 与 Integration Event

在同一个 bounded context 内,Domain Event 可以协调多个 aggregate 的业务反应。

跨 bounded context / service 对外发布时,通常需要稳定的 Integration Event contract。

所以:

Domain Event
≠ 必然直接发到 message broker

需要有明确 boundary mapping。

Repository

Repository 用领域语言表达 aggregate 的 persistence access。

例如:

orderRepository.findById(orderId)
orderRepository.save(order)

它的目标不是把 SQL 包一层而已,而是:

让 domain/application 层依赖 aggregate collection-like contract,而不是数据库细节。

DDD 与数据库表不是一一对应

常见错误:

一张表
= 一个 Entity
= 一个 Aggregate
= 一个 Service

这些属于不同设计层。

真实 domain model 可能:

  • 一个 aggregate 跨多张表;
  • 多个 bounded context 使用同一个业务 ID,但模型不同;
  • Value Object 被映射成多列;
  • read model 完全不同于 write domain model。

DDD 与 CQRS / Event Sourcing / Microservices

DDD 可以和这些方法组合,但不等于它们:

DDD ≠ CQRS
DDD ≠ Event Sourcing
DDD ≠ Microservices
DDD ≠ Clean Architecture

是否采用这些架构,要根据复杂度和实际约束决定。

什么时候不值得用重型 DDD

如果系统主要是:

  • 简单 CRUD;
  • 业务规则少;
  • 模型很稳定;
  • 团队没有复杂领域知识协作;

完整 tactical DDD 可能带来:

  • 过度抽象;
  • 文件/层级膨胀;
  • 大量无价值 mapper/service/repository;
  • 学习和维护成本。

DDD 的价值来自管理领域复杂度,不是让目录看起来更高级。

一个最小建模流程

真实业务场景
→ 和领域专家统一语言
→ 识别 Subdomain
→ 定义 Bounded Context
→ 找核心 invariant
→ 识别 Entity / Value Object
→ 定义 Aggregate boundary
→ 将 workflow 放 Application Service
→ 将业务规则留在 Domain
→ 用 tests/examples 验证模型

常见失败模式

Anemic Domain Model

Entity 只有 getter/setter,所有规则都在 Service。

→ 领域模型退化成数据库 DTO。

Giant Aggregate

所有相关对象都塞进一个 aggregate。

→ consistency boundary 过宽。

技术术语替代业务语言

接口叫:

updateStatus()
processData()
doAction()

而不是业务真实动作。

为了 DDD 强拆微服务

先拆 deployment,再寻找 domain boundary。

→ 顺序反了。

Repository Everywhere

每个表一个 repository。

→ 这是数据访问模板,不一定是 DDD。

学习检查

  1. DDD 最主要解决的是什么复杂度,而不是哪种技术栈?
  2. Ubiquitous Language 与 Bounded Context 为什么必须一起理解?
  3. Bounded Context 为什么不等于 Microservice?
  4. Entity 与 Value Object 的判断标准分别是什么?
  5. Aggregate boundary 应由 foreign key 还是 invariant 决定?
  6. Domain Service 与 Application Service 有什么区别?
  7. Domain Event 为什么不等于数据库 row updated?
  8. 为什么简单 CRUD 系统未必值得完整 tactical DDD?
  9. 给一个“订单 + 支付 + 库存”系统,尝试划分至少两个 bounded contexts,并说明同一个 Order/Customer 概念是否必须共享同一模型。
创建于 2025/8/10 更新于 2026/9/30
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本