type
status
date
slug
summary
tags
category
icon
password
1、领域驱动(DDD)是什么
领域驱动设计(Domain-Driven Design,简称DDD)是一种以业务领域为核心的软件设计方法论,由Eric Evans在其2003年的著作《Domain-Driven Design: Tackling Complexity in the Heart of Software》中提出。它的核心目标是通过深入理解业务领域,将复杂的业务逻辑清晰映射到软件系统中,从而构建更灵活、可维护的代码。
以 DDD(四层模型)和传统MVC(三层模型)进行对比。
1.1 传统MVC应用架构
传统 MVC 应用架构:通常是围绕数据库表为起点进行整体系统设计,分为三层:
- 模型(Model)
- 视图(View)
- 控制器(Controller)
以电商系统为例,MVC 模型的实现如下:
这种设计结构的缺点:
- MVC模式仅仅反应了软件层面的架构,它不包含业务语言,无法使用该设计直接和业务对话。
- 面向数据库编程,数据库模型只是数据库映射,服务层围绕数据库完成业务逻辑,造成数据和行为天然割裂。
- 缺乏明确的边界划分,至少在顶层设计层面没有边界划分的规范要求,更多地是靠技术负责人根据经验进行划分,大规模团队协作容易出现职责不清晰、分工不明确。
1.2 DDD应用架构
DDD分层结构中将三层架构中的业务逻辑拆解为应用层和领域层,核心业务逻辑表现下沉到领域层去实现,以业务领域模型为核心建模(面向对象建模)。这种设计更能体现对现实世界的抽象,通常分为四层:
- 用户界面层:主要职责是通过用户界面向用户显示数据信息,同时解释用户的命令,并把用户的请求发送到应用层。
- 应用层:通过调用基础设置和领域层完成数据资源操作及业务流程编排,相当于BS层。
- 领域层:将业务逻辑高度内聚到领域层,所以领域层是整个系统的核心,它只与实际业务相关,不关心任何技术细节,尽可能做到与持久化无关。
- 基础设施层:包含了任何类型的框架、数据库访问代码或者公共的方法等,纯技术的一层。
以电商系统为例,参考文章最后的电商 DDD 模型的实现。
这种设计结构的特点:
- 统一语言:团队(业务方、产品、设计、技术等)在一个限定的上下文中有意识地形成对事物统一的描述,从而形成统一的概念(模型)。统一语言用于需求文档、PRD文档、系分文档、代码以及日常沟通中,统一的概念和术语可以极大地提升沟通效率和工作效率。
- 面向业务建模:领域模型和数据模型分离,业务复杂度和技术复杂度分离。DDD聚焦于领域模型,将技术实现细节从模型中剥离出来,能够更好地降低业务和技术的耦合度。
- 边界清晰的设计方法:通过对需求的识别及分类,划分出领域、子域和限界上下文,进而指导团队成员分工协作,从而做到将复杂的问题分而治之地解决。
- 业务领域的知识沉淀:通过模型与软件实现关联,统一语言与模型关联,反复论证和提炼模型,使得模型与业务的真实世界保持一致,从而促使业务知识通过模型得以传递和沉淀。
DDD的优点:
- 业务与代码对齐:减少需求误解,提升软件可维护性。
- 灵活性:通过清晰的边界,更容易应对业务变化。
- 可测试性:领域模型独立于基础设施,便于单元测试。
DDD的缺点:
- 学习曲线陡峭,需要团队对业务有深入理解。
- 过度设计风险,简单场景可能无需DDD。
- 需要领域专家持续参与。
DDD的适用场景:
- 业务逻辑复杂,需要长期迭代的系统(如金融、电商、ERP)。
- 需要跨团队协作,且业务领域存在多个子域(Subdomain)。
- 传统架构(如MVC)导致业务代码混乱,难以维护时。
2、DDD的核心概念
- 限界上下文(Bounded Context):定义模型的边界。例如,电商系统中“订单”在物流和支付上下文中可能具有不同的含义和属性。
- 统一语言(Ubiquitous Language):在相同的限界上下文内,业务人员和开发团队使用一致的术语,避免沟通歧义。例如,“订单”在电商系统中需明确定义其属性和行为。
- 领域模型(Domain Model):包含领域对象、属性、关系、行为、边界范围等各个方面,用于描述业务的本质,是领域驱动模型的核心产出。
- 实体(Entity):具有唯一标识的对象(如用户、订单)。
- 值对象(Value Object):不可变对象,通过属性定义的对象(如地址、金额)。
- 聚合根(Aggregate Root):一组相关对象的入口,保证业务一致性(如“订单”聚合根包含订单项)。
- 领域服务(Domain Service):处理无状态业务逻辑(如转账服务)。
- 领域事件(Domain Event):记录业务状态变化(如“订单已支付”)。
- 仓储(Repository):封装数据访问逻辑,提供聚合根的持久化。
限界上下文(Bounded Context)
限界上下文是指将一个复杂领域划分为多个明确的边界,每个边界内有一套独立的领域模型(代码和术语)。不同上下文中的同名概念可能含义不同。
限界上下文的特点:
- 语义边界:同一术语在不同上下文中可能有不同含义(例如电商中的“订单”在物流上下文中可能仅关注配送,而在支付上下文中关注金额)。
- 自治性:每个上下文内的模型、代码和数据库可独立设计,无需与其他上下文强一致。
- 高内聚性:每一个上下文内部紧密组织,职责明确,具有较高的内聚性。
- 明确关系:通过上下文映射(Context Mapping)定义不同上下文间的交互方式(如合作、共享内核等)。
限界上下文示例:在实际工程中,通常用一个模块来表示一个领域的限界上下文。电商系统可能的上下文:
- 产品目录上下文:管理商品信息。
- 库存上下文:跟踪库存数量。
- 订单上下文:处理订单生命周期。
- 物流上下文:处理配送逻辑。
虽然都涉及“产品”,但:
- 产品目录中的“产品”包含描述、图片;
- 库存中的“产品”仅关注SKU和数量。
限界上下文和子域的区别:
- 子域:反映业务能力的自然划分(核心域、支撑域、通用域),属于问题空间(Problem Space)。
- 界定上下文:是解决方案空间(Solution Space)的实现边界,一个子域可能对应多个界定上下文。
例如电商系统中,“订单”子域可能拆分为:
- 订单处理上下文(处理创建、状态流转)
- 订单支付上下文(处理支付逻辑)
统一语言(Ubiquitous Language)
团队(业务方、产品、设计、技术等)在一个限定的上下文中有意识地形成对事物统一的描述,从而形成统一的概念(模型),这些统一的描述和统一的概念就是统一语言。统一语言主要源自于领域模型的概念与逻辑,作为对业务维度的补充和展开,也会将限界上下文、系统隐喻等纳入到统一语言中。
以“RabbitAdvisors”商业模式中的这句话“用户可以选择自己感兴趣的专栏进行付费订阅”,进行简单的建模。

根据这个模型,我们可以形成统一语言:
- 用户(User)是指所有在“RabbitAdvisors”注册过的人。(来自领域模型概念)
- 订阅的专栏(Subscription)是指用户付费过的专栏。(来自领域模型概念)
- 用户可以订阅多个专栏。(来自领域模型逻辑)
- 订阅。(来自限界上下文)
通过定义与解释,我们使这些词语在其所使用的上下文中没有歧义。再通过这些基础词汇,去描述业务的行为或者规则,慢慢就可以将其确立为跨业务与技术的统一语言了。统一语言是在使用中被确立的。
有了统一语言后,我们就可以很方便的描述需求:
用户(User)可以查阅自己订阅过的专栏(Subscription),也可以查看其中的教学内容。
也可以用来描述测试用例:
当用户(User)已购买过某个专栏(Subscription),那么当他访问这个专栏时,就不需要再为内容付费。
这里仅仅举了两个使用统一语言的场景,当所有工种角色都接受它,用它去描述业务和系统的时候,它才会成为真正的统一语言。
实体(Entity)
实体是具有唯一身份标识且可变的领域对象,是通过身份标识而非属性来定义的对象。
实体的特点:
- 唯一标识:每个实体都有一个唯一标识,即使其属性发生变化,这个标识保持不变,那就还是同一个实体
- 可变性:实体的属性可以随时间改变
- 生命周期连续性:实体在其整个生命周期中保持身份连续性
- 业务行为:实体包含业务逻辑和行为,而不仅仅是数据容器
实体实例:
- Customer(客户):有客户ID作为唯一标识
- Order(订单):有订单号作为唯一标识
- Product(产品):有产品SKU作为唯一标识
值对象(Value Object)
值对象是指不可变的对象(一旦创建就不能改变),是通过其属性(值)而非身份标识来定义的对象,可以与其他值对象进行相等性比较。
值对象的特点:
- 无标识性:没有唯一标识符,通过属性值来区分
- 不可变性:创建后状态不能被修改
- 概念整体性:代表一个完整的概念,即使包含多个属性
- 可替换性:可以整个替换而不只是修改部分属性
- 值相等性:当所有属性值相等时,两个值对象被视为相等
值对象示例:
- 货币:由金额和币种组成,$100和¥100是不同的值对象
- 地址:包含国家、城市、街道等属性
- 日期范围:开始日期和结束日期组合
- 颜色:由RGB值或十六进制代码表示
- 坐标:经度和纬度组合
值对象与实体(Entity)的区别
- 实体:通过标识区分,即使属性相同但标识不同就是不同对象。
- 值对象:通过属性值区分,没有唯一标识,属性相同就是相同对象。
特性 | 值对象(Value Object) | 实体(Entity) |
标识 | 由属性值决定 | 有唯一标识(ID) |
可变性 | 不可变 | 可变 |
生命周期 | 可随意创建/丢弃 | 有明确的生命周期管理 |
相等比较 | 基于所有属性值 | 基于标识符 |
例子 | 货币、日期、地址 | 用户、订单、产品 |
聚合根(Aggregate Root)
聚合根是一个特定聚合的根实体(Root Entity),外部访问聚合内所有对象的唯一入口,负责维护聚合内部的一致性和完整性。
聚合根的特点:
- 全局标识:聚合根具有全局唯一的标识,聚合内的其他实体可以只有本地标识
- 访问控制:外部对象只能通过聚合根来访问聚合内的其他对象
- 事务边界:一个聚合通常被视为一个事务边界,修改一个聚合内的多个对象被视为一个原子操作
- 一致性维护:聚合根负责确保聚合内的所有对象保持一致状态
聚合根示例:
- Order(订单)可能是一个聚合根
- 它包含 OrderItem(订单项)实体和 Address(地址)值对象
- 外部代码只能通过 Order 来修改其中的 OrderItem 或 Address。
领域服务(Domain Service)
领域服务用于封装领域逻辑(业务规则),用来串联领域模型(例如聚合根)、资源库(持久化操作)和防腐层(调用外部接口)等一系列领域内的对象的行为,为其他上下文提供交互的接口。
领域服务的特点:
- 无状态性:领域服务本身不持有业务状态(数据),仅提供操作逻辑。状态由传入的领域对象(实体或值对象)承载。
- 跨对象协作:当一个业务逻辑涉及多个实体/值对象的交互时,领域服务可以协调这些对象完成任务,避免将逻辑分散到某个实体中。
- 依赖领域术语:领域服务的命名和操作直接反映业务语言(Ubiquitous Language),例如
TransferService
(转账服务)、OrderApprovalService
(订单审核服务)。
- 与基础设施服务区分:领域服务专注于业务逻辑,不处理技术细节(如数据库访问、网络调用)。技术实现通常由应用服务或基础设施层完成。
领域服务的适用场景:
- 当业务逻辑不属于单个实体的职责时(例如:跨实体的转账、复杂的校验规则)。
- 当操作需要访问外部系统(如调用风控服务、生成唯一ID)。
- 当逻辑无法合理放入实体中(避免污染实体的单一职责)。
领域服务和应用服务的区别
- 领域服务:封装核心业务逻辑,属于领域层,使用领域对象(实体/值对象)进行操作。
示例:计算运费、校验订单合规性。
- 应用服务:协调领域层和基础设施层,处理用例流程(如事务管理、权限校验),属于应用层。
示例:用户下单的流程(调用领域服务、保存数据库、发送通知)。
领域事件(Domain Event)
领域事件用于捕获业务领域中发生的重要事实或状态变化。它表示在业务上下文中已经发生的、对业务有实际意义的事情,通常由领域内的某个动作触发,并可能引发后续反应。
领域事件的特点:
- 业务意义:领域事件反映的是业务人员关心的具体事件(如“订单已支付”“库存不足”“用户注册成功”),而非技术细节(如“数据库记录更新”)。
- 过去时态:事件名称通常使用过去时(如
OrderCancelled
、PaymentReceived
),强调它已经发生,不可更改。
- 轻量级通知:事件本身仅包含必要的数据(如事件ID、发生时间、相关聚合根的ID等),不包含复杂逻辑。
- 解耦作用:事件允许不同组件(甚至不同限界上下文)以松耦合的方式对事件作出反应,避免直接依赖。
领域事件的作用:
- 业务一致性:当核心状态变更时,可能需要触发其他业务逻辑(例如,订单创建后发送通知)。
- 系统集成:跨微服务或模块间通过事件通信,避免强依赖。
- 审计与溯源:记录事件可用于重建历史状态(如结合事件溯源模式)。
仓储(Repository)
仓储充当领域模型和存储层之间的抽象层。仓储只提供统一接口,具体实现可以由多种存储技术实现,例如数据库、分布式缓存、本地缓存、文件系统等。
仓储的特点:
- 只处理聚合根(Aggregate Root),不直接处理聚合内的子对象
- 仓储可以基于ORM(如Entity Framework)实现,但不暴露ORM特性
- 可以在仓储中实现缓存逻辑以提高性能
仓储的作用:
- 保持领域模型的纯净性,不掺杂持久化细节
- 提供灵活的数据访问策略切换能力
- 使领域模型更容易测试(可通过Mock仓储)
防腐层(Anti-Corruption Layer)
防腐层是一个隔离层,位于核心领域模型与外部系统之间,用于保护核心领域模型免受外部系统或遗留系统的污染。
防腐层的作用:
- 转换外部模型与内部领域模型之间的数据
- 隔离外部系统的变化对核心领域的影响
- 防止外部系统的概念和模式渗透到核心领域
防腐层实现方式:
- 适配器模式(Adapter):将外部系统的接口转换为领域模型理解的接口,处理协议、数据格式的差异
- 门面模式(Facade):为复杂的外部系统提供简化的接口,隐藏外部系统的复杂性
- 转换器(Translator):在两种不同模型间进行双向转换,通常包含映射逻辑
- 防腐服务(Anti-Corruption Service):封装所有与外部系统的交互,实现领域语言与外部语言的转换
3、DDD的分层架构
3.1 经典四层架构

- 用户接口层/表示层(Presentation Layer)
- 负责向用户显示信息
- 解释用户命令
- 包含Web界面、API接口、CLI等
- 不应包含业务逻辑
- 应用层(Application Layer)
- 协调应用程序活动
- 不包含业务逻辑
- 负责事务管理、安全验证
- 调用领域层完成业务操作
- 包含应用服务(Application Services)
- 领域层(Domain Layer)
- 核心领域模型,包含业务逻辑和规则
- 领域层通常包含以下内容:
- 实体(Entity)
- 值对象(Value Object)
- 聚合根(Aggregate Root)
- 领域服务(Domain Service)
- 领域事件(Domain Event)
- 仓储接口定义(Repository Interface)
- 依赖的外部服务接口定义(ThirdParty Interface)
- 基础设施层(Infrastructure Layer)
- 提供技术支持
- 实现持久化、消息传递等
- 包含仓储实现、外部服务调用等
- 为上层提供技术实现细节
各层通信规则:
- 上层可以调用下层,但下层不应调用上层
- 领域层应保持纯净,不依赖其他层
- 基础设施层实现领域层定义的接口
- 跨层调用通常通过DTO或领域对象
这种分层方式有助于保持领域模型的核心地位,使业务逻辑与技术实现分离,提高系统的可维护性和可扩展性。
3.2 贫血模型和充血模型
领域层的实现通常分为充血模型和贫血模型两种主要模式,它们代表了完全不同的设计哲学。
3.2.1 贫血模型(Anemic Domain Model)
特点:
- 领域对象只包含数据(属性),不包含业务逻辑
- 业务逻辑放在服务层(Service Layer)中
- 领域对象主要是简单的getter/setter集合
优点:
- 简单直观,容易理解
- 与许多ORM框架配合良好
- 适合简单的CRUD应用
缺点:
- 违反了面向对象的基本封装原则
- 业务逻辑分散在各处,难以维护
- 领域对象缺乏行为,只是数据结构
示例代码:
3.2.2 充血模型(Rich Domain Model)
特点:
- 领域对象既包含数据也包含行为
- 业务逻辑封装在领域对象内部
- 服务层很薄,主要负责协调领域对象
优点:
- 符合面向对象设计原则
- 业务逻辑集中,内聚性高
- 领域模型更能反映真实业务
缺点:
- 设计复杂度较高
- 需要更深入的领域知识
- 与某些ORM框架配合可能有挑战
示例代码:
3.2.3 如何选择模型
两种领域模型类型没有绝对的优劣之分,可以根据具体应用场景选择合适的模型。
充血模型适用场景:
- 业务逻辑复杂且可能变化
- 需要长期维护的项目
- 团队具备良好的OO设计能力
贫血模型适用场景:
- 业务逻辑简单
- 主要是CRUD操作
- 开发周期紧张
- 团队对OO理解不深
现代 DDD 实践通常推荐尽可能使用充血模型,因为它能更好地应对复杂业务逻辑的变化,但也要根据实际项目情况做出权衡。
4、DDD实战(电商系统)
以电商系统中订单模块为例:
4.1 领域层
实体:订单项
值对象:价格
聚合根:订单聚合订单项(实体),商品聚合价格(值对象)
仓储:订单和商品的仓储接口
领域服务:在领域服务中聚合订单和商品两个聚合根的业务逻辑
领域事件:订单发布领域事件
防腐层:调用外部系统
4.2 应用服务层
- Author:mcbilla
- URL:http://mcbilla.com/article/1e185c7d-7c1d-802e-8dcb-c1a6c2ff2fe5
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts