思维转变
领域驱动设计(Domain-Driven Design,以下简称 DDD)的核心价值在于其对「业务领域」的深度聚焦。这里的「领域」并非单纯的技术范畴,而是指代软件系统所要映射的现实业务场景及其核心价值主张。DDD 通过建立与业务高度契合的领域模型,使得技术实现与业务本质形成同频共振,从而有效解决复杂业务场景下的认知鸿沟问题。
在 VUCA(Volatile 易变性、Uncertain 不确定性、Complex 复杂性、Ambiguous 模糊性)特征愈发显著的现代商业环境中,任何架构设计都面临固有局限。这种局限性既源于业务需求本身的动态演进,也受制于人类认知的有限性——正如 Eric Evans 在开山之作中强调的“模型永远是对现实的近似抽象”。但正是这种局限性,凸显了 DDD 方法论的战略意义:
它通过"战略设计"构建业务全景图,运用限界上下文划定领域边界,通过"战术设计"落地聚合根、实体/值对象等模式,形成应对复杂性的结构化解决方案。
需要特别指出的是,DDD 的复杂性并非方法论本身的缺陷,而是其应对现实业务复杂度的必要代价。这种复杂性体现在三个维度:
- 认知复杂性:要求开发团队与领域专家共建"通用语言",实现业务概念与代码模型的精准映射。
- 架构复杂性:通过分层架构实现业务逻辑与技术实现的解耦,采用防腐层处理系统集成问题。
- 演进复杂性:借助子域划分和上下文映射,为持续演进的业务提供可扩展的架构基础。
对于实践者而言,DDD 的价值不在于提供完美无缺的终极方案,而是为 VUCA 环境下的系统建设提供基础性指引。其核心思想——无论是通过限界上下文实现的领域自治,还是通过聚合根维护的业务一致性——都为控制软件熵增提供了可落地的模式库。即便不完全采用DDD 完整体系,其领域建模思想、分层架构理念等核心要素,仍能显著提升复杂系统的可维护性和演进能力。这种开放包容的哲学,恰是 DDD 历经二十年仍保持生命力的关键所在。
贫血模型 vs. 充血模型
- 贫血模型:指的是只有属性而没有行为的模型。
- 充血模型:指的是既有属性又有行为的模型。
笔者过往的实践中,基本上都使用类似于
controller→service→repository[model]
的三层架构:
conrtoller
负责暴露对外接口。service
负责执行所有的业务逻辑。repository
复杂数据的存储和缓存,包含数据对象model
的定义。
在这个模式下,基本上所有的核心逻辑都充斥在 service
层中,所以 service
层一般都会非常大,它要扮演多面手,即要负责跟各个模块协作,还要负责处理具体的业务规则,最终完成一个业务行为。这个过程中,model
即为贫血模型,因为逻辑都给 service
处理了,这种架构也称为贫血三层架构。
在 DDD 的理念下,很多的核心业务概念都会被建模为「领域对象」,这些「领域对象」本身就是一种业务规则的体现,所以把业务的处理逻辑,都归属到这些「领域对象」的行为当中了,即所谓的充血模型。
在这个理念下,一个优化后的充血四层架构如下图所示:

贫血模型推荐场景:业务简单、迭代快速、团队技术栈偏传统(如 Spring Boot+MyBatis)时,避免过度设计。
充血模型推荐场景:业务复杂、需长期演进(如核心交易系统)、团队具备 DDD 经验时,通过实体、值对象、领域服务等战术设计理念降低系统熵增。
混合使用的场景:部分核心领域用充血模型(如订单、支付),非核心模块用贫血模型(如日志、配置),平衡效率与质量。
实际上,充血模型因其状态完整,适合进行状态变更类的操作,以确保业务操作符合领域规则;贫血模型由于其轻量级,更适合作为不会涉及状态变更的操作的数据容器。这其实就是 CQRS 的理念。
概念清单
战术设计
实体
定义:会随着业务变化发生变化的业务概念叫作实体对象。
关键点:实体需要唯一表示
值对象
定义:一些对象在表达业务概念时是必须的,可业务并不围绕着它们进行,它们仅是对这些重要业务概念的描述,这一类对象叫作值对象。
关键点:
- 值对象的意义取决于属性,只要对象的属性一模一样,那么对象就是相同的。
- 尽量把值对象实现为不可变对象。
领域服务
定义:领域服务自身是没有数据的,只是表达了某种业务计算逻辑,或者业务的某种策略。
关键点:
- 领域服务是无状态的。
- 只有在确实表达了一个相对独立的业务概念或者业务策略,并且不能简单地把它归结到某个既有的业务对象上时,才是一个真正的领域服务。
领域事件
定义:领域事件代表从业务专家视角看到的某种重要的事情发生了。
关键点:
- 领域事件是一种特殊的值对象。
- 应该根据限界上下文中的通用语言来命名事件:AccountActivited。
- 应该将事件建模成值对象或贫血对象。
聚合
定义:聚合从本质上讲是在基础的构造块上增加了一层边界,用边界把那些紧密相关的对象放到了一起。
关键点:
- 紧密相关的对象存在数据一致性问题;
- 缺乏边界时,维护数据一致性是困难的;
- 划分边界的关键在于既不要让整个系统成为一个整体,又让每个单独划分出的聚合具有明确的业务意义;
- 聚合需要关注三条法则:
- 生命周期一致性:如果一个对象在聚合根消失之后仍然有意义,那么说明此时在系统中必然存在能够访问该对象的方法。这和聚合的定义矛盾,所以聚合内的其他元素必然在聚合根消失后失效。
- 问题域一致性:不属于同一个问题域的对象,不应该出现在同一个聚合中。
- 尽量小的聚合:聚合的本质作用是提升对象系统的粒度,确保一致性、降低复杂度。不过,粒度绝不是越大越好。如果聚合的粒度太大,那内部的逻辑复杂度也会大大增加还会影响到复用度。因此,要能够比较容易地断开聚合。
资源库
定义:对于查询、创建、修改、删除数据的操作,领域模型使用“资源库(Repository)”这个概念来承载它们。
关键点:一个聚合对应一个资源库,应以聚合根命名资源库,除了聚合根之外的其他对象,都不应该提供资源库对象。
工厂
定义:工厂用于构建聚合。
关键点:一个聚合往往包含多个对象,这些对象的数据之间又可能存在联系,如果允许分别创建这些对象,就会让聚合是业务完整性的单元这个定义面临失败。
战略设计
统一语言
定义:与业务专家协作定义全团队通用的术语表,消除沟通歧义。
关键点:
- 同一个概念在不同的上下文中可能存在不同的含义;
- 同一个概念在同一上下文中的不同环节,也可能存在不同的含义,需要非常明确清晰的界定,降低沟通成本。
子域
定义:子域是对业务领域的逻辑划分,用于分解复杂问题。通常分为核心子域(业务核心竞争力)、支撑子域(辅助核心业务)和通用子域(可复用的标准化能力)。
关键点:因业务目标、团队定位和组织发展阶段等方面的不同,这三个子域的划分并非一成不变,而是会互相转换。
限界上下文
定义:限界上下文本质上是一个自治的小世界,它有完备的职责,还有清晰的边界。
关键点:
- 一个子域的一切资产,包括领域模型、数据库、包、可执行程序、接口声明等,都应该封装在限界上下文中,避免跨越边界。
- 如何平衡边界的价值和不利影响,是划分边界时要做的一种重要取舍。一个较为稳妥的策略是考虑认知的渐进特征,不要过早隔离。在已经确定的边界上进行划分,延缓划分那些尚具模糊性的边界,在这些边界逐渐变得清晰时再分离它们。
上下文映射
定义:限界上下文约定了基于领域模型的架构层次的设计分解,而分解必然意味着集成和协作。上下文映射就是对限界上下文之间的协作关系的模式总结。
关键点:
- 在边界上完成概念映射是一种基本模式。通过在应用层组装或者使用适配器完成概念映射,可以保持领域概念的清晰,避免领域模型遭到不必要的污染。
- 防腐层模式、标准开放服务模式、客户-供应商模式、追随者模式。
串讲
在应对复杂业务系统时,DDD 通过分治策略将业务领域拆分为多个子域(如电商系统的订单、支付子域),每个子域对应一个限界上下文——这是技术与业务对齐的关键边界,既承载领域模型的实现,也通过上下文映射(如防腐层、共享内核等模式)实现跨子域协作,避免模型污染。
限界上下文内的领域对象是业务逻辑的载体:具备唯一标识和生命周期的实体(如订单实体通过 ID 跟踪状态变化)、描述特征且不可变的值对象(如地址由省市构成,修改需整体替换),以及通过聚合根统一操作保证一致性的聚合(如订单聚合根管理订单项和配送信息)。当业务逻辑跨越多个聚合时,由无状态的领域服务协调(如支付计算需整合订单、账户聚合)。
对象的创建与持久化分别由工厂(封装复杂初始化逻辑)和资源库(隔离存储细节)负责,而领域事件(如订单支付成功事件)则驱动跨上下文的异步协作。
战术设计
factory
- factory 用于构建复杂的领域对象。
repository
- 只有聚合根有 repository。
- repository 就只提供
load
和save
功能,且要保证事务一致性。 - 尽可能提供行级的 repository,而不是表级的 repository,对于表级的 repository,可以抽成一个领域服务。
设计模式
责任链模式
将请求的发送者和接受者解耦,使多个对象都有机会处理请求。
- 责任链模式的使用要点在于要将维护责任链的代码和业务代码分开。
- 在 DDD 中使用责任链模式时,应创建一个领域服务,在领域服务中完成责任链的创建和执行。
- 尽量不要在责任链的处理器中通过
set
修改领域对象(聚合根)的状态,责任链应仅用于某些值的计算,最终将计算结果交给聚合根完成业务操作。
笔者实现了一个快速构建责任链的工具:
策略模式
允许在运行时根据需要选择不同的实现。
- 在 DDD 中使用策略模式时,通常先定义一个领域服务接口,再在其实现类中完成策略的加载、选择和执行。
- 注意屏蔽策略模式的实现细节,避免上层关注领域服务内的设计模式细节。
桥接模式
旨在通过解耦抽象和实现,使两者能够独立扩展和变化。
- 多维解耦机制:桥接模式通过组合/聚合关系替代继承关系,将原本紧密耦合的抽象层(功能定义)与实现层(具体操作)分离例如遥控器(抽象)与电视(实现)的协作,遥控器通过接口控制电视,无需关注具体品牌。
- 正交扩展能力:支持两个独立变化维度(如消息类型与通知渠道、图形与渲染方式),避免类数量呈指数级增长(M×N 组合问题)。电商物流系统中,新增微信通知渠道时,无需修改所有消息类即可实现扩展。
规约模式
规约模式是一种用于定义业务领域中规则和约束的模式,通常由规约接口(Specification)和验证器(Validator)两个部分组成。
- 在 DDD 中,规约模式并不是在聚合根进行业务操作之前做前置校验,而是在聚合根完成业务操作之后做后置校验,确保 Repository 保存的聚合根符合业务规则。
适配器模式
将被适配者(Adaptee)的接口转换为目标接口(Target),使原本因接口不兼容而无法协同工作的类能够协同。
- 在 DDD 中,可以使用适配器模式来实现防腐层,以将外部上下文接口(如开放主机服务)返回的模型转换为本地上下文定义的领域模型,并将本地上下文的操作转换为对外部上下文的操作。可以有效隔离外部上下文的领域模型,避免互相污染。
领域事件
幂等性
领域事件的定义
领域事件是领域模型的组成部分,它通常由聚合根产生,并被其他聚合或者限界上下文订阅和处理,触发相应的业务逻辑。
注意点:
- 应该根据限界上下文中的通用语言来命名事件:AccountActivited。
- 应该将事件建模成值对象或贫血对象。
应用:
- 解耦领域对象之间的关系;
- 触发其他领域对象的行为;
- 记录领域内已发生的状态变化;
- 实现跨聚合的最终一致性;
- 进行限界上下文集成。
消息体:
1 | { |
领域事件的生成
- 应用层创建领域事件。
- 聚合根创建领域事件。
要避免在聚合根内部调用基础实施发布领域事件,而是生成后返回给应用层,由应用层去发布。
1 | type Entity struct { |
领域事件的发布
- 直接发布并轮询补偿:为事件存储一个发布状态标识,用于记录是否补发成功。并提供定时任务检索超时未发布成功的事件进行重新发布。
- 采用事务日志拖尾:引入变更数据捕获组件(Change Data Capture,简称 CDC),捕获数据的变更日志,解析后获得领域事件并发布。
领域事件的订阅
将领域事件订阅者放置在用户接口层
user-interface-subscriber
,收到事件后调用应用服务执行业务逻辑。
事件溯源
事件溯源(Event Sourcing)是一种将所有的领域事件(Domain Event)存储到事件存储(Event Store)中,并通过重放历史事件来还原领域对象状态的模式。
核心思想是将系统中所有的状态变更都视为事件,将这些事件以事件顺序记录下来,并存储到事件存储中。这样,可以通过重放这些事件,来还原任意时刻的系统状态。
三种方案:
- 通过回放所有的历史事件重建聚合根。
- 通过快照提高重建聚合根的效率。
- 通过拉链表生成所有事件对应的快照。
拉链表是一种用于处理缓慢变化维度问题的数据结构,它可以有效地处理维度数据的历史变化。在拉链表中,每个记录都有一个开始时间和结束时间,用于描述该记录的存活时间,即该记录的有效期。

CQRS
CQRS 将系统的操作分为两类:
- 命令(Command):负责数据的写操作(增、删、改),不返回数据。
- 查询(Query):负责数据的读操作,仅返回结果且不修改数据。
两者的数据模型可独立设计,甚至使用不同的数据库或存储技术。
适用场景
应对高并发读写场景
案例 1:B 站点赞系统
在日均活跃用户近亿的 B 站,点赞功能通过 CQRS分离读写操作。写入端通过消息队列(如Kafka)异步处理请求,避免数据库锁竞争;查询端通过缓存优化读取性能,显著提升系统吞吐量和稳定性。
案例 2:实时答题 PK 游戏
高并发的答题得分计算场景中,CQRS 结合事件溯源(EventSourcing)记录每个操作事件,确保读写模型的最终一致性,同时支持复杂战况数据的实时展示。
解决复杂查询需求
案例 3:电商订单查询
随着订单查询需求多样化(如按时间筛选、跨实体聚合数据),CQRS通过独立读模型简化查询逻辑,避免领域模型被复杂查询逻辑污染。
案例 4:微服务数据聚合
在微服务架构中,CQRS允许通过事件同步跨服务数据到专用读库,避免跨服务联表查询的性能瓶颈(如行程管理服务与用户信息服务的聚合查询)。
提升数据模型灵活性
案例 5:文本增量更新
针对大型文本编辑场景,CQRS拆分读写模型,增量保存修改记录并通过事件合并,减少网络传输数据量,同时支持任意版本的历史数据恢复。
不适用场景
- 简单 CRUD 系统(如小型管理后台)
- 强一致性要求的金融交易场景(如实时扣款)
- 团队缺乏事件驱动架构经验时
一致性
聚合内事务实现
- 聚合内事务控制不要放在应用层,会使应用层承担过多的责任。应用层应专注于协调领域对象和基础设施以完成业务操作,不应过多涉及数据访问和事务控制的细节。
- 聚合内事务控制可以交给
Repository
来实现,采用乐观锁解决并发问题,可以基于版本号和时间戳,一般重试 1-3 次即可。
聚合间事务实现
聚合间控制可以单独建立一个领域服务 Domain Service 来完成。
对于实时性要求不高,仅需最终一致性,可以使用本地消息表或者最大努力通知的方案。
对于实时性一致性要求比较高,可以采用 TCC(Try-Confirm-Cancel) 事务方案。
对于长事务场景,或者涉及外部系统、遗留系统,可以考虑 Saga 事务方案。
Saga 将事务分为多个事务,这些分支事务按照一定的顺序执行。当某个分支事务执行成功后,会通过消息通知下一个分支执行;当某个分支事务执行失败时,会按照正常事务执行顺序的相反方向进行一系列的补偿操作,以确保全局事务的一致性。
战略设计
事件风暴
核心概念与元素
元素名称 | 颜色标识 | 说明 |
---|---|---|
领域事件(Domain Event) | 橙色 | 表示已发生的业务事实,以“动词过去式”命名(如“订单已提交”),是事件风暴的核心起点。 |
命令(Command) | 深蓝色 | 触发领域事件的操作或意图(如“提交订单”),通常由用户或系统触发。 |
参与者(Actor) | 黄色 | 执行命令的角色,包括用户、部门或外部系统(如“客户”触发支付命令)。 |
外部系统(External System) | 粉色 | 与当前系统交互的第三方服务(如支付网关回调生成事件)。 |
策略(Policy) | 紫色 | 业务规则或约束条件(如“库存不足时取消订单”),决定事件触发的逻辑。 |
读模型(Read Model) | 绿色 | 为查询优化的数据视图(如“用户订单列表”),支持决策展示。 |
聚合(Aggregate) | 大黄色 | 业务对象集合(如“订单聚合”包含订单项和状态),维护一致性和完整性。 |
问题(Question) | 红色 | 未达成共识的争议点(如事件定义分歧),需后续专项讨论。 |
实施流程与步骤
准备工作
- 参与人员:业务专家、开发、产品、测试等跨职能角色,需领域专家主导。
- 物料:多色便签、白板、马克笔,线上工具辅助远程协作。
- 参与人员:业务专家、开发、产品、测试等跨职能角色,需领域专家主导。
识别领域事件 团队通过头脑风暴罗列所有可能事件(如电商场景的“订单已创建”“库存已扣减”),按时间轴排列,争议事件用红色便签标记并暂存。
补充命令与角色 为每个事件关联触发命令及执行者(如“客户”执行“支付订单”命令生成“支付完成”事件),区分内部操作与外部系统调用。
定义策略与读模型 添加业务规则(如“订单金额≥1000元需审核”)和数据展示需求(如“实时库存看板”)。
构建聚合与划分子域 将相关事件、命令归类为聚合(如“支付聚合”),划分限界上下文(如“订单服务”“库存服务”),明确微服务边界。
注意事项
事件粒度的把控:避免过度细化(如“用户已睁眼")或过于宽泛(如“订单已修改”),需聚焦业务关键节点。
争议处理与迭代:对未达成共识的事件标记为“问题”(红色便签),后续专题讨论;定期回顾模型,修正错误或补充遗漏。
技术实现衔接 :事件风暴的输出需转化为代码模型,例如通过事件溯源(Event Sourcing)持久化事件流,或结合 CQRS 分离读写逻辑。
C4 架构模型
层级 | 核心目标 | 受众 | 关键元素 |
---|---|---|---|
Context(上下文) | 描述系统与外部实体(用户、第三方系统)的交互关系 | 非技术人员(如业务方、客户) | 系统边界、用户角色、外部依赖(如支付网关) |
Container(容器) | 展示系统内部的高阶技术组件(进程级单元) | 技术管理者、架构师 | Web 应用、数据库、消息队列等独立进程单元,关注技术选型与通信协议(如 REST API、gRPC) |
Component(组件) | 细化容器内部的业务模块与交互逻辑 | 开发团队 | 服务、模块、接口(如订单服务、库存服务),强调职责划分与依赖关系 |
Code(代码) | 展示组件实现的代码结构 | 开发者 | 类、方法、数据库表(如 UML 类图、ER 图),通常由 IDE 工具自动生成 |
除了四层核心视图,C4 模型还提供:
- 部署图:展示容器在物理环境中的分布(如 Kubernetes 集群部署)。
- 动态图:描述业务流程(如用户下单到支付完成的时序交互)。
- 系统景观图:多系统协同的全局视图(如企业级中台架构)。
实践案例
参考作者的 ddd-archetype
,笔者实现了一个 Go 版本的 ddd-archetype-go
:
整体架构如下: