Domain-driven design

2026-04-211 出链1 引用
  • S:在学习和实践“Domain-driven design”时,需要建立统一的概念和问题边界。
  • C:相关概念、实现机制和工程取舍容易混在一起,导致只记住结论而无法判断适用条件。
  • Q:“Domain-driven design”要解决什么问题?它的核心抽象、工作机制和边界分别是什么?
  • A:本文围绕“Domain-driven design”整理核心概念、关键机制和工程实践,并明确其适用边界。

用限界上下文控制模型扩散,用聚合控制一致性,用领域事件和集成模式连接整个系统。

  1. 先谈边界(限界上下文),再谈对象(实体、值对象)
  2. 聚合是事务一致性边界(还有并发粒度)
  3. Repo 服务于领域模型(聚合的持久化/取回边界,不侵入领域模型)
  4. 业务里发生了事实(领域事件,连接一切:聚合之间、上下文之间的协作机制)

domain

领域(Domain)是软件要解决的整个业务问题空间。领域按职责可拆分为多个子域(Subdomain),每个子域对应一块相对独立的业务问题。

子域按业务价值和差异化程度分为三类:

  1. 核心域(Core Domain):公司核心竞争力所在,投入最优资源精心建模(如外卖平台的智能调度算法)
  2. 支撑域(Supporting Subdomain):有企业特有逻辑但非核心,难以直接外购,适度投入(如商家入驻审核流程)
  3. 通用域(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) 表示领域中已发生的业务事实。

  • 过去时命名:OrderPlacedPaymentReceivedInventoryReserved
  • 不可变,一旦发布不可修改
  • 用途:
    • 聚合内部:记录状态变更
    • 聚合之间:触发跨聚合的副作用(最终一致性)
    • 上下文之间:作为集成事件(Integration Event)驱动跨服务协作

patterns

  1. CQRS Command Query Responsibility Segregation
    • 将"写模型"和"读模型"分离;适合读写差异大、查询复杂的系统,但会增加复杂度
  2. Event Sourcing
    • 不存当前状态,只存事件流,状态由事件回放得出;常与 CQRS 搭配
  3. Saga
    • 无中央协调者,每个参与者监听事件并决定下一步或补偿;适合简单线性流程
  4. Process Manager
    • 有中央协调者维护状态机,决定流程走向和补偿;适合复杂、有分支的跨聚合/服务长事务
  5. Hexagonal Architecture Ports & Adapters
    • DDD 常见的落地架构。领域核心通过端口定义接口,外部依赖通过适配器接入,实现内外解耦
  1. https://martinfowler.com/bliki/BoundedContext.html
  2. https://martinfowler.com/bliki/DomainDrivenDesign.html
  3. https://www.dddcommunity.org/library/vernon_2011
  4. https://www.jimmybogard.com/domain-driven-refactoring-intro
  5. https://udidahan.com/2008/02/15/from-crud-to-domain-driven-fluency
  6. https://udidahan.com/2009/06/29/dont-create-aggregate-roots/

评论