Domain-driven design
- S:在学习和实践“Domain-driven design”时,需要建立统一的概念和问题边界。
- C:相关概念、实现机制和工程取舍容易混在一起,导致只记住结论而无法判断适用条件。
- Q:“Domain-driven design”要解决什么问题?它的核心抽象、工作机制和边界分别是什么?
- A:本文围绕“Domain-driven design”整理核心概念、关键机制和工程实践,并明确其适用边界。
用限界上下文控制模型扩散,用聚合控制一致性,用领域事件和集成模式连接整个系统。
- 先谈边界(限界上下文),再谈对象(实体、值对象)
- 聚合是事务一致性边界(还有并发粒度)
- Repo 服务于领域模型(聚合的持久化/取回边界,不侵入领域模型)
- 业务里发生了事实(领域事件,连接一切:聚合之间、上下文之间的协作机制)
domain
领域(Domain)是软件要解决的整个业务问题空间。领域按职责可拆分为多个子域(Subdomain),每个子域对应一块相对独立的业务问题。
子域按业务价值和差异化程度分为三类:
- 核心域(Core Domain):公司核心竞争力所在,投入最优资源精心建模(如外卖平台的智能调度算法)
- 支撑域(Supporting Subdomain):有企业特有逻辑但非核心,难以直接外购,适度投入(如商家入驻审核流程)
- 通用域(Generic Subdomain):行业通用、无差异化,能买就买、能外包就外包(如认证、权限、短信通知)
同一功能在不同公司归属不同:支付对支付宝是核心域,对普通电商是通用域。分类的目的是决定资源分配——把最好的资源投在核心域上。
bounded context
限界上下文(Bounded Context) 是模型的有效边界。同一个词在不同上下文中含义不同(如"产品"在商品上下文是详细信息,在订单上下文只是 ID+名称)。
- 一个子域可以有多个 BC,但一个 BC 不应跨子域
- 每个 BC 内部有自己的通用语言(Ubiquitous Language),团队和代码共用同一套术语
- BC 之间通过明确的接口通信,内部模型互不污染
上下文映射(Context Mapping)
上下文之间的协作关系模式:
| 模式 | 含义 |
|---|---|
| Shared Kernel | 两个 BC 共享一小部分模型,双方协商变更 |
| Customer-Supplier | 上游(Supplier)提供,下游(Customer)消费,上游尊重下游需求 |
| Conformist | 下游无话语权,被动适配上游模型 |
| Anti-Corruption Layer (ACL) | 下游建翻译层隔离上游模型,防止外部概念侵入自身领域 |
| Open Host Service | 上游提供公开协议/API 供多个下游使用 |
| Published Language | 用标准化格式(如 JSON Schema、Protobuf)作为交换语言 |
entity / value object / aggregate
实体(Entity)
有唯一标识(ID)、生命周期可变的对象。用 ID 判等,而非属性。
例:
Order(id=1001)和Order(id=1002)即使内容相同也是不同的订单。
值对象(Value Object)
无唯一标识、不可变、用全部属性判等。
例:
Money(amount=100, currency="CNY")—— 两个 Money 只要 amount 和 currency 相同就是"相同的"。
聚合(Aggregate)
一组紧密关联的实体和值对象,以聚合根(Aggregate Root) 为入口,构成事务一致性边界。
- 外部只能通过聚合根引用和操作内部对象
- 聚合之间只通过 ID 引用,不持有对方的对象引用
- 一个事务只修改一个聚合;跨聚合一致性用领域事件(最终一致性)
- 把行为放进实体,避免贫血模型(如
order.cancel()而非orderService.cancel(order))
例:
Order(聚合根)包含OrderItem(子实体)和ShippingAddress(值对象)。
Repository
每个聚合一个 Repository(不是每张表一个)。接口定义在领域层,实现在基础设施层。
Repository 只负责聚合的持久化和取回,不侵入领域模型的行为。
domain event
领域事件(Domain Event) 表示领域中已发生的业务事实。
- 用过去时命名:
OrderPlaced、PaymentReceived、InventoryReserved - 不可变,一旦发布不可修改
- 用途:
- 聚合内部:记录状态变更
- 聚合之间:触发跨聚合的副作用(最终一致性)
- 上下文之间:作为集成事件(Integration Event)驱动跨服务协作
patterns
- CQRS
Command Query Responsibility Segregation- 将"写模型"和"读模型"分离;适合读写差异大、查询复杂的系统,但会增加复杂度
- Event Sourcing
- 不存当前状态,只存事件流,状态由事件回放得出;常与 CQRS 搭配
- Saga
- 无中央协调者,每个参与者监听事件并决定下一步或补偿;适合简单线性流程
- Process Manager
- 有中央协调者维护状态机,决定流程走向和补偿;适合复杂、有分支的跨聚合/服务长事务
- Hexagonal Architecture
Ports & Adapters- DDD 常见的落地架构。领域核心通过端口定义接口,外部依赖通过适配器接入,实现内外解耦
links
- https://martinfowler.com/bliki/BoundedContext.html
- https://martinfowler.com/bliki/DomainDrivenDesign.html
- https://www.dddcommunity.org/library/vernon_2011
- https://www.jimmybogard.com/domain-driven-refactoring-intro
- https://udidahan.com/2008/02/15/from-crud-to-domain-driven-fluency
- https://udidahan.com/2009/06/29/dont-create-aggregate-roots/
评论