DDD 概念理解-理论篇
领域驱动设计(Domain Driven Design,DDD),DDD为我们提供了一种架构设计方法,既面向业务,又面向技术,从业务需求到领域建模,从领域服务到技术转化,强调开发人员与领域专家协同。DDD的核心思想是业务与技术相结合的一种过程,既强调业务的理解,又强调应用领域建模方法的使用。DDD本质上是面向对象分析的扩展和延伸,它基于面向对象分析技术进行了分层规划,同时对其中的核心概念和划分做了
本文是理论篇,志在讲明白DDD是个什么东西,主要思想是什么?都有哪些内容,期待看完后大家能够了解DDD。
- 至于想知道DDD 的代码结构,请期待后续更新
1. DDD 是什么?
1.1. 什么是DDD?
领域驱动设计(Domain Driven Design,DDD),DDD为我们提供了一种架构设计方法,既面向业务,又面向技术,从业务需求到领域建模,从领域服务到技术转化,强调开发人员与领域专家协同。
DDD的核心思想是业务与技术相结合的一种过程,既强调业务的理解,又强调应用领域建模方法的使用。DDD本质上是面向对象分析的扩展和延伸,它基于面向对象分析技术进行了分层规划,同时对其中的核心概念和划分做了详细的指引。(理解是更深层次、全面的面相对象思维)
说人话就是,把现实世界高度的复杂问题,边界更清晰的拆分,梳理明白,然后开发同学根据业务特性进行系统、应用、模块拆分,从而更好的拥抱业务变化,控制需求变动,代码修改的范围。
1.2. 为什么需要DDD?
对于一个架构师来说,在软件开发中如何降低系统复杂度是一个永恒的挑战,无论做业务,还是做平台、中台,大家常常会被交错复杂的业务逻辑、晦涩耦合的业务代码搞得心力交瘁,常常会面临如下的问题。
- **复杂系统设计:**复杂的业务系统,扩展性下降,系统多,业务逻辑复杂,概念不清晰,有什么合适的方法指导帮助我们理清楚边界,逻辑和概念
- 多团队协同:没有很好的划分拆解,各模块间相互依赖较深,改动影响较大,边界不清晰,语言不统一导致沟通和理解困难。有没有一种方式把业务和技术概念统一,大家用一种语言沟通。例如:航程是大家所理解的航程吗?
- 设计与实现不一致:PRD,详细设计和代码实现天差万别。一个业务需求的变动,代码上下游都需要不小的改动,有什么方法可以把业务需求快速转换为设计,同时还要保持设计与代码的一致性?
- 架构统一,可复用资产和扩展性:当前取决于开发的同学具备很好的抽象能力和高编程的技能。有什么好的方法指导我们做抽象和实现。
解法:DDD的核心本质在于降低软件复杂度的手段,通过领域建模,实现业务(战略设计)和代码(战术设计)的一致,从而技术能够跟随业务共同发展,随着业务的变化,很好的控制代码的变化范围,从而从降低软件的复杂度,拥有更好的扩展性,大大缓解上述问题。
1.3. 什么时候用DDD?
DDD 是的为了解决业务模型高度复杂,业务复杂性高于项目的技术特性的项目。所以是否使用DDD 需要判断你的系统业务是否足够复杂。
- 你的系统是否以数据为中心,所有操作都是数据库CRUD?
- 业务逻辑是否只是少量的业务场景和用例?
- 当前应用功能是否稳定?
- 是否已经对业务领域足够了解?
如果以上问题的答案基本都是“是”,说明系统并没有复杂的业务逻辑,则可以用一般的面向数据的架构或者事务脚本等模式。但如果业务逻辑复杂、变化频繁、团队对该领域还缺乏一定的认知,需要进行领域模型和服务的梳理,那么DDD会帮助我们抽象和解决问题。
1.4. DDD怎么解决这些问题
DDD 通过战略设计和战术设计两部分,实现业务(战略设计)和代码(战术设计)的一致。
战略设计主要从业务视角出发,建立业务领域模型,划分领域边界,建立通用语言的限界上下文,这些限界上下文可以作为微服务设计的参考边界。
战术设计则从技术视角出发,着重于领域模型的技术实现,包括聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现。
在战略设计过程中,领域模型的建立是重中之重。为此,DDD 提出了事件风暴这一建立领域模型的方法。事件风暴是一个从发散到收敛的过程,通常采用用例分析、场景分析和用户旅程分析,全面分解业务领域,梳理领域对象之间的关系,这是一个发散的过程。在事件风暴过程中,会产生很多实体、命令、事件等领域对象,我们将这些领域对象从不同的维度进行聚类,形成如聚合、限界上下文等边界,建立领域模型,这就是一个收敛的过程。
因此,DDD 可以帮助软件工程师建立清晰的领域模型,划分业务和应用边界,以指导微服务的设计和拆分。事件风暴是建立领域模型的主要方法,通过其发散的过程和聚合的过程,建立起合理的领域模型,从而实现高效的软件开发和落地。

2. DDD 基本概念


2.1. 战略设计
2.1.1. 统一语言
在事件风暴过程中,开发团队和领域专家交流达成共识的,能够简单、清晰、准确地描述业务含义和规则的语言就是通用语言。通用语言是团队的统一语言,不管你在团队中单身什么样的角色,在同一个领域的软件生命周期里都是用统一的语言践行交流。软件模型实现的也是通用语言,软件模型的源代码就是通用语言的书面表达方式。通用语言必须严谨、精确、紧凑。
统一语言,解决交流障碍,提高沟通效率,确保业务需求的正确表达和系统的正确实现。
2.1.2. 领域
领域(Domain)在DDD中指一个业务问题的整体环境和背景,它包含了业务逻辑、规则、流程和概念,是软件系统要解决和实现的业务内容的抽象。
领域从字面意思解释是从是一种专门活动或事业的范围、部类或部门。具体指一种特定的范围或区域。既然是范围,就要有边界,领域的划定的过程其实就是边界确立的过程,
DDD 的领域指的是这个边界内要解决的业务问题域,在解决业务问题时,DDD会按照一定的规则对业务领域进行细分,当领域细分到一定程度后,DDD会将问题范围限定在特定的边界内,在这个边界内建立领域模型,进而用代码实现该领域模型,解决响应的业务问题。
既然领域是用来想定业务边界和范围的,那么就会有大小之分,领域越大,业务边界的范围越大,反之则相反,领域可以进一步划分为子领域。我们把划分出来的多个子领域称为子域,每个子领域对应一个更下哦的问题或更下小的业务范围。
2.1.2.1. 子域
子域(Subdomain)是对领域的进一步细分。每一个子域对应一个更小的问题域或更小的业务范围。代表某一业务功能或业务流程的子集。在一个复杂的领域中,可能包含多个子域,每个子域对应特定的业务功能。子域仍可以分为核心子域(Core Subdomain)、支撑子域(Supporting Subdomain)和通用子域(Generic Subdomain),每种子域在业务中的重要性和实现难度不同。

- 核心域: 重要业务的领域。业务属性重、企业特点重,决定产品或企业核心竞争力的能力子域就是核心子域。比如树的果实、花 这些树上重要的部位。比如保险领域就是承保、收付、理赔等。
- 通用域: 很通用的领域,没有太多个性化的诉求,可以被多个子域重复使用,但几乎没有企业特点。甚至通过采购可以获得。比如认证、权限等等
- 支撑域: 具备企业特性,业务属性不是很强、或者不具备通用性,但又是企业必须的。比如数据字典系统,比如树的茎和叶,这些可能会与果实竞争营养的部分。
核心域可以依赖通用域和支撑域,但反过来不行。
2.1.3. 限界上下文
限界上下文分成两个词: 限界和上下文。
- 限界:指具体的领域边界;
- 上下文:业务语义所在的上下文环境,也就是语义环境(例如电商场景,一个商品在销售阶段是商品,在运输阶段叫货物,由于的业务领域边界不同,通用语言的术语就有不同的含义)。
综上,限界上下文就是在限定的上下文环境内,用来封装通用语言和领域对象,保证领域内的一些术语、领域对象等有一个确切的含义,没有语义的二义性的一个业务边界。
它定义了领域模型的边界和业务使用范围,使得团队所有成员能够给明确的指导,什么内容应该在领域模型中实现,什么不应该在模型中实现,保证模型一致性和完整性的框架。
举个例子:
企业在设置组织架构(部门、处室等)时,就是在定义切的限界上下文边界。设置组织架构会职能边界出发,划定部分的边界,比如 “人力资源部”、“财务部”、“后勤部”等。部门的职能边界,就是业务组织架构的限界上下文边界。部门内部聚集域部门职能相关的所有角色,这些角色就类似领域模型的领域对象。
确定了部门的职责边界后,不同部门之间职能不应该出现重叠混淆,也不应该将与部门职能不想关的角色放在部门内部,否则就会破坏组织架构的限界上下文边界。
简单来说:限界就是用来确定领域边界的规则。从领域模型图上来看的话,限界是就是领域和领域之间的边界线。上下文是这个边界内部的领域的规则和语境。
2.1.3.1. 上下文映射
上下文映射(Context Mapping)表示了一组不同的限界上下文在解决空间中如何通过集成所形成的相互关联和依赖关系。
2.2. 战术设计
2.2.1. 实体
实体和值对象是组成领域模型的最基础的单元。
实体(Entity)是具有唯一身份标识的对象,其身份标识在整个生命周期内保持不变,对这些对象而言,重要的不是属性,而是其延续性和标识,对象的延续性和标识会跨越甚至超出软件的生命周期,这种领域对象称为实体。(核心特征是标识,而不是属性。)
常见的实体如: 订单,用户。简单来说就是:身份标识是不可变的,实体的属性可以变化,但不影响他的身份。
实体在不同环节的形态。
- 业务形态:[战略设计] 多个属性、操作或行为的为一体,可以根据命令、业务操作或者事件,这些业务行为作用的实体对象。
- 代码形态:[战术设计] 表现为实体类,采用充血模型,封装自身的属性和方法(跨多个实体的领域逻辑在领域服务中实现)
- 运行形态:[战术设计] 以领域对象(DO)的实例形式存在,可以对其属性进行多次修改,修改后因ID不变,依然是同一个实体。
- 存储形态:[战术设计] 一个领域对象(DO)可能会对应0、1或多个数据持久化对象(PO),持久化对象更偏向于整体性能的考虑,和领域对象的设计并不绝对耦合。
2.2.2. 值对象
相对实体来说值对象会更加抽象一些
值对象:没有唯一标识,值对象通常是不可变,值对象通过其属性值来判断相等性,是将多个相关属性组合为一个概念整体,用于描述领域的某个特定方面。它们通常表示一些描述性概念,如货币、地址等。
值对象本质上就是一个集合。这个集合里面有若干个用于描述目的、具有整体概念和不可修改的属性,常见的实体如: 地址,将“省、市、县和街道”等属性,构成一个地址的属性集合,这个属性集合就是地址值对象。

值对象在不同环节的形态
- 业务形态:[战略设计] 和实体一样包含若干个属性的集合,不包含业务逻辑。
- 代码形态:[战术设计] 表现为实体类的属性,没有ID,会被实体整体引用。
- 运行形态:[战术设计] 以领域对象(DO)的成员变量形式存在,不涉及修改数据行为。
- 存储形态:[战术设计] 往往作为属性嵌入到某个实体的表中,多数情况下存在扩展字段中。
Tips: __当一个对象由其标识(而不是属性)区分时,这种对象称为实体(Entity)。当一个对象用于对事务进行描述而没有唯一标识时,它被称作值对象(Value Object)。
2.2.3. 聚合(Aggregate)
实体和值对象都只是个体化的业务对象,是个体的行为和能力。我们需要一个组织,将这些琳咪关系的个体对象聚集在一起,按照组织内统一的业务规则共同完成特定的业务功能,因此就有了聚合的概念,聚合表现的则是整体业务能力。
聚合(Aggregate):由业务和逻辑紧密关联的实体和值对象的组合,聚合内数据的修改必须由聚合根统一组织,确保每次数据修改都是按照聚合内部统一的业务规则完成,聚合是数据修改和持久化的基本单元。(过去传统数据模型中,可以在业务逻辑是现实时,找到实体或数据库表完成数据修改,这在DDD中是不允许的。)
聚合的设计目的是为了确保业务数据的一致性和完整性,同时简化对象之间的关系管理。每个聚合内必须要有一个聚合根 (Aggregate Root) 来统领聚合的业务能力。
例如:订单聚合就有自己的业务规则:订单总金额等于所有商品明细金额之和”等
每一个聚合都有设计一个仓储完成聚合数据的持久化操作,为了避免聚合数据频繁变化,尽可能将聚合内变更的数据,封装在一次提交仓储完成持久化。
2.2.4. 聚合根(Aggregate Root)
聚合根(Aggregate Root)是这个聚合的根节点。是访问聚合的唯一入口,管理着聚合内其他对象的生命周期和完整性。聚合根是一种特殊的实体,是对聚合内数据模型统一控制的规则,保证聚合内部的实体的数据逻辑一致性。
聚合根通常是这个聚合的专家,它的本质也是一个实体。聚合根负责管理聚合内部对象的关系,并控制对聚合内部对象的访问。
- 聚合根的职责:
- 事务边界:
- 身份标识:聚合根是实体,聚合根通常有一个唯一的标识符,用于在系统中唯一地标识整个聚合。
- 管理控制:聚合的管理者,聚合内,负责协调实体和值对象,按照固定的业务规则,共同完成聚合共同的业务逻辑。
- 访问控制:聚合之间以聚合根ID关联的方式接受聚合的外部任务和请求,外部系统只能通过聚合根来访问聚合内部的对象。
再举一个生活中的例子,在班级值日的场景中,聚合好比一个值日组,聚合根则可以比作这个组的组长。在这个组中大家各显神通,有会扫地的组员、有会擦黑板的组员、还有会擦窗户的组员。组长本身也有很重要的职责,同时他还要负责协调组员一起工作。老师想要询问这个组的值日情况,必定是问组长。组长整体向老师负责。
聚合的设计原则:
聚合作为DDD对象体系中的一层,应该遵循高内聚、低耦合的原则。聚合的设计满足如下5个规则:
- 在一致性边界内建模真正的不变条件:聚合是拆分微服务最小业务单元
- 事务边界:聚合根定义了事务的边界,确保聚合内部的操作是原子的。
- 生命周期一致性:聚合边界内的对象,和聚合根之间存在“人身依附”关系。即:如果聚合根消失,聚合内的其他元素都应该同时消失。
- 问题域一致性:不属于同一个问题域的对象,不应该出现在同一个聚合中
- 场景频率一致性:场景操作频率的一致性是同一聚合内部对象的一个关键表征。经常被同时操作的对象,它们往往属于同一个聚合。而那些极少被同时关注的对象,一般不应该划为一个聚合。
- 设计小聚合:聚合内的元素尽可能少,聚合出现的本质是解决一致性问题带来的复杂性,聚合包含过多实体和值对象会导致逻辑实现负责,高频操作可能会出现并发冲突,导致系统可用性变差。
- 通过唯一标识引用其他聚合:为了聚合间的解耦,聚合之间通过引用聚合根ID的方式,可以将聚合根ID 作为服务参数,进行跨聚合的领域服务调用,后续聚合拆分到不同微服务时间时,改动也会很小
- 在边界外使用最终一致性:聚合内采用数据抢一致性,聚合之间采用最终一致性,DDD强调一次事务中最多只修改一个聚合的数据,如果一个业务操作涉及多个聚合数据修改,应采用领域事件驱动。
- 应用层实现跨聚合的服务调用:聚为避免领域层聚合之间耦合,通过应用层应用服务来组织和协调各个聚合的领域服务,解耦并实现跨聚合的服务调用。
2.2.4.1. 工厂
为了让聚合根专注于领域模型,在DO对象创建时,需要确保聚合根和它的依赖对象同时被创建,如果这项工作由聚合根实现,那么聚合根的构造函数逻辑将会非常负复杂。
当创建一个对象或创建整个聚合时,如果创建工作很复杂,或者暴露了过多的内部结构,可以使用工厂来进行封装。也就是说,将创建复杂对象的实例和聚合的职责转移到一个单独的对象,这个对象本身在领域模型中可能没有职责,但它也是领域设计中的一部分。
把工厂作为一种创建复杂对象和聚合的实现方式。工厂用来封装对象创建所必需的知识,当聚合的根建立时,所有聚合包含的对象将随之建立。
设计模式中的工厂类和工厂方法与领域模型中的工厂概念是相似的,其可以帮助我们封装复杂的对象创建过程。
聚合和领域服务差异:
聚合根和领域服务都可以组合多个实体对象完成复杂的领域逻辑,但为了避免聚合根的业务逻辑过于负责,避免聚合根类代码量过于庞大,建议聚合根除了承担它聚合管理职能外,只作为实体实现与聚合根自身行为相关的业务逻辑,而将跨多个实体的负责领域逻辑统一放在领域服务中实现。当然简单聚合的跨多个实体的领域逻辑,可以考虑在聚合根方法中实现。
2.2.5. 存储库 (Repository)
为了解耦领域逻辑和数据处理逻辑,让领域层更关注领域逻辑实现,在领域层和基础层增加存储层。
存储库的主要职责,封装对数据库的操作,实现对象的持久化,解耦领域内业务逻辑与底层持久化,使得领域层可以通过简单的接口与数据存储层交互,而无需直接操作数据库。
一个聚合会有一个存储库,统一由存储库来完成聚合数据的持久化
2.2.6. 领域事件(Domain Event)
领域事件:是指在领域中发生的事件,这个事件一定是领域中某个重要操作的结果。一个领域事件的发生通常还会导致进一步的业务操作。它在实现领域模型姐欧的同时,还有助于形成完成的业务操作闭环。
领域事件的设计目的是为了: ① 解耦系统 ② 记录业务变化 ③ 触发后续操作
常见的领域事件如: 用户下单完成后,生成一个订单创建事件,用户支付完成后生成一个支付完成事件。在店铺系统中,领域事件如: 店铺信息提交审核后,生成一个 草稿提交成功事件
不能将领域事件和状态机划等号,领域事件一定是业务上能操作出来的,有业务意义的。而状态机是一种实现方案。例如,在店铺业务中,草稿的状态有 [初始化]、[待提单]、[提单失败]、[提单成功]、[审核失败]、[审核成功]、[发布中]、[发布失败]、[发布成功]。其中有业务含义的可能只有 [提单成功] (对应草稿提交操作) 、[审核成功] (对应草稿审核通过操作)、[发布成功] (对应草稿信息生效动作)。
一般情况下,领域事件的发生确实对应着实体状态的流转,但这也不是绝对的。例如在店铺的业务场景中,通常会有把店铺划给某一个BD进行管辖的操作,这就可以定义为 店铺归属关系创建/变更事件,而这个事件的本身并不会导致某些实体状态的变化。
3. 分层架构
微服务架构模型由好多种,如“洋葱架构”、“CQRS”和“六边形”架构等,这些架构模式核心理念都是为了设计出“高内聚,低耦合”的系统,实现架构演进。
DDD分层架构,包括四个层级:User Interface, Application, Domain, Infrastructure

- 用户接口层Facade:负责与不同用户和应用之间的交互协议和数据格式的转换
- 应用层:应用层是很薄的一层,职责是协调领域层多个聚合完成服务的组合和编排,它不包含业务规则或知识,而是将业务逻辑的执行委托给领域层。应用层的主要任务是组织和分配工作,
- 应用层还负责事件的订阅和发布,以及与其他外部服务的交互,事件的具体实现则在领域层
- 领域层:是领域模型的核心,它负责表达业务知识,掌握业务状态的控制权,实现企业核心业务逻辑,领域层主要体现领域模型的业务能力。
- 跨多个聚合的领域逻辑在领域层实现,由领域服务组织和协调多聚合的多实体,实现原子业务逻辑
- 领域层关注实现领域对象的充血模型和聚合本身的原子业务逻辑,至于用户操作和业务流程,则交给应用层去编排。这样设计可以保证领域模型不容易受外部需求变化的影响,保证领域模型的稳定
- 领域服务(Domain Service)封装不属于任何实体但涉及多个实体的业务逻辑的服务。当业务逻辑涉及多个实体且不适合放在某个具体实体中时,可以使用领域服务来封装这些逻辑。领域服务通常是无状态的。
- 基础设施层:作用就是为其它各层提供通用的技术和基础服务。包括但不限于: 第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等
3.1. 传统三层架构向DDD分层架构的演化方式

3.2. 几种架构模型的对比分析
3.2.1. 六边形架构
2025年 Alistair Cockburn 提出 六边形架构,又名“端口适配器”架构


六边形架构的核心原则: 核心业务能力(上图中红圈范围内的)与一切外部资源完全隔离,它们仅与适配器交互,由适配器来和外部交互。
- 内六边形: 实现应用的核心业务逻辑
- 外六边形: 完成外部应用、驱动和基础资源等的交互和访问。对前端应用以 API 主动适配的方式提供服务,对基础资源以依赖倒置被动适配的方式实现资源访问 (和整洁架构思想一致)
- 领域层依赖倒置:领域层依赖基础层倒置成基础层依赖领域层,这个简单的变化使得领域层不依赖任务层,其他层都依赖领域层,使得领域层只表达业务逻辑且稳定。
3.2.2. 洋葱架构
2008年 Jeffre Palermo 提出

洋葱架构:最主要的原则就是依赖原则,定义了个层的依赖关系,越往内层依赖越低,代码级别越高,越是核心能力。外圆依赖只能指向内圆,内圆不需要知道外圆的任何情况。
洋葱架构个的层次:
- 领域模型: 实现领域内核心业务逻辑,它封装了企业级的业务规则。领域模型的主体是实体,一个实体可以是一个带方法的对象,也可以是一个数据结构和方法集合。
- 领域服务: 实现涉及多个实体的复杂业务逻辑。
- 应用服务: 实现与用户操作相关的服务组合与编排,它包含了应用特有的业务流程规则,封装和实现了系统所有用例。
- 最外层 主要提供适配的能力,适配能力分为主动适配和被动适配。主动适配(用户界面): 主要实现外部用户、网页、批处理和自动化测试等对内层业务逻辑访问适配。被动适配(基础资源): 主要实现核心业务逻辑对基础资源访问的适配,比如数据库、缓存、文件系统和消息中间件等。
| 对比内容\架构 | DDD分层架构 | 洋葱架构 | 六边形架构 |
|---|---|---|---|
| 职责划分 | 三种架构均有非常清晰的分层,且各层职责明确 | ||
| 核心焦点 | 追求每层都能独立扩展 | 核心焦点在领域能力与基础资源分离, 领域层能力和基础资源各自可独立扩展 |
核心焦点在外部适配器的扩展和维护, 外部适配器易于扩展和替换 |
| 落地和维护成本 | 在三种架构中相对较容易落地和维护,没有严格的避讳, | 在三种架构中相对较难落地和维护 关键在于基础资源依赖领域能力会有些反直觉,理解成本相对较高 |
在三种架构中相对较难落地和维护。 关键点在于识别适配器,分离变与不变 |
| 适用场景 | 适用于领域模型较复杂、业务逻辑较清晰的项目。整体上较为通用 | 适用于大型复杂系统,特别是底层基础依赖容易替换的系统 | 适用于需要与外部有很多交互的系统 |
参考:
《中台架构与实现基于DDD和微服务》机械工业出版社
更多推荐


所有评论(0)