目录
[DDD 领域驱动设计概念](#DDD 领域驱动设计概念)
[DDD 的目标](#DDD 的目标)
[DDD 的适用场景](#DDD 的适用场景)
[DDD 体系名词解析](#DDD 体系名词解析)
[DDD 的建设](#DDD 的建设)
[DDD 建模总结](#DDD 建模总结)
[DDD 的分层架构](#DDD 的分层架构)
[三层架构到 DDD 四层架构的转化](#三层架构到 DDD 四层架构的转化)
[DDD 代码架构](#DDD 代码架构)
本部分内容主要来源于鱼皮智能协图云图库部分,并在笔者个人项目学习的基础上进行扩展衍生。由于项目开发文档已经足够详细,因此这里只记录要点。
软件架构模式的演进
传统单体架构
所有的应用功能都集成在一个单一的应用程序中,所有模块和组件都在同一个进程内运行,请求直接操作数据库,不进行代码分层,易于开发和部署,尤其适合小型或简单的应用。
随着业务增长和需求变更,单体架构变得难以扩展和维护。不同功能的模块耦合在一起,导致更新某个功能可能影响到整个系统。

分层架构
应用被划分为不同的层(如业务接入层、业务逻辑层、数据访问层等),每一层负责特定的功能,层与层之间通过接口进行交互,促进了模块化和职责分离,便于管理和维护。
但层与层之间的紧密耦合限制了灵活性,且随着系统的复杂度增加,可能导致性能下降和维护难度增加,并且它的可扩展性和弹性伸缩性差。

微服务架构
将系统拆分为多个小而独立的服务,每个服务负责处理一组特定的功能,每个服务通常由独立的团队开发、部署和维护,服务之间通过轻量级协议(如 HTTP、自定义协议或消息队列)进行通信。
服务之间独立,易于扩展和维护。每个微服务都可以独立部署、开发和扩展,且易于使用不同的技术栈。

DDD 领域驱动设计概念
DDD(领域驱动设计,Domain-Driven Design) 是一种软件开发方法论和设计思想 。DDD 通过领域驱动设计方法定义领域模型,从而确定业务和应用的边界,保证业务模型和代码模型的一致性。
DDD也常与微服务一起谈论,微服务的难点是业务的拆分边界的确定。而而 DDD 就是一个方法论,指导我们根据领域模型确定业务的边界,从而划分出应用的边界,最终落实成服务的边界、代码的边界。像是微服务的前阶段。
DDD 的目标
- 通过领域模型实现业务需求:开发者与领域专家共同理解业务需求,形成共享语言并构建模型。
- 提高系统的灵活性与可维护性:通过合理划分限界上下文,减少系统的耦合度,使得不同模块或子系统可以独立演化。
- 支持复杂业务逻辑的表达:通过深入的业务建模,使得复杂的业务逻辑能够清晰、准确地反映在代码中。
总结一下,就是让系统更贴合业务,让大型系统更利于独立建设和维护。
DDD 的适用场景
- 业务复杂的系统:如金融系统、电商平台等,涉及的业务逻辑复杂且频繁变化。
- 需要与多个部门或团队合作的项目:DDD 强调跨部门协作,适用于多方参与的大型项目。
- 长周期、长期维护的项目:DDD 强调可维护性与演化,适合需要长期维护和扩展的系统。
总结一下,大型的、跨部门协作的、长期维护的复杂项目。
DDD 体系名词解析
实体
业务对象,有唯一标识与属性,有自己的生命周期。
值对象
没有唯一标识,创建后不能修改,只能用另一个值整体替换。比如地址
聚合
实体和值对象是基础的领域对象,聚合将多个实体和值对象组合成一个整体,实现高内聚低耦合。
聚合根
聚合根就好比聚合内的带头人,聚合内的多个实体不会直接对外提供接口访问,而是由聚合根统一提供对外接口。
一个聚合内只会有一个聚合根,聚合根通过对象引用的方式组织聚合内的实体和值对象,聚合根之间的合作是通过 ID 关联的。
聚合根也是一个实体,也具有业务属性和业务逻辑和唯一标识。
领域
领域指系统关注的业务领域或问题空间,具体的领域与公司或组织的核心业务有关。
实际上在 DDD 中 领域就是用来确定范围,而范围就是边界。
一个领域又可以分为多个子领域,每个子领域代表系统的一部分业务。
而子域根据重要程度和功能特性,可划分为:
-
通用域:指系统中一些通用的、不特定于某一业务的领域,它们在多个不同领域或系统中都有应用。(例如支付、日志管理)
-
支撑域:指在系统中起到支持作用,但并不是直接驱动业务价值的部分(例如网关)
-
核心域:指系统中最关键的部分,是业务的核心竞争力所在,能够为企业带来最大的价值
限界上下文
类似语境的东西,用来限定语义边界。(怎么有点德里达的味道)
是指一个明确的边界,规定了某个子领域的业务模型和语言,确保在该上下文内的术语、规则、模型不与其他上下文冲突。
在事件风暴讨论过程中,我们需要完成通用语言的统一。例如电商场景下,我们统一叫物品为商品、将用户购买商品的行为叫下单。
领域服务
聚合根可以实现跨多个实体的复杂业务行为,但是为了实现高内聚和低耦合,聚合根内部应该更聚焦与自身强关联的业务行为,复杂的跨多实体的业务可以放在领域服务中实现。
领域服务是指那些 不能归属于某个单一实体或值对象,但又属于领域模型的一部分 的业务逻辑。领域服务封装了对领域对象进行操作的核心业务规则,通常用于处理跨多个实体的操作,或者当业务逻辑无法直接归属于某个特定聚合时。
充血模型与贫血模型
| 特点 | 贫血模型 | 充血模型 |
|---|---|---|
| 封装性 | 数据和逻辑分离 | 数据和逻辑封装在同一对象内 |
| 职责分离 | 服务类负责业务逻辑,对象负责数据 | 对象同时负责数据和自身的业务逻辑 |
| 适用场景 | 简单的增删改查、DTO 传输对象 | 复杂的领域逻辑和业务建模 |
| 优点 | 简单易用,职责清晰 | 高内聚,符合面向对象设计思想 |
| 缺点 | 服务层臃肿,领域模型弱化 | 复杂度增加,不适合简单场景 |
| 面向对象原则 | 违反封装原则 | 符合封装原则 |
1)缺血模型
上面贫血模型的示例可以视为缺血模型的一种表现形式。缺血模型实际上是贫血模型的进一步简化或极端化版本。
在缺血模型中,不仅对象没有业务逻辑,甚至服务层也缺乏真正的业务逻辑,系统的整体设计趋向于 CRUD(增删改查)开发,会将所有逻辑转移到外部。
2)涨血模型
涨血模型则是充血模型的极端化表现,不仅将所有核心业务逻辑集中于领域模型中,甚至连非核心逻辑(如数据库事务处理、权限校验等)也全部包含其中。
DDD 的建设
DDD 会先建立领域模型,根据业务划分领域边界,进而确定微服务的边界,然后再根据领域分块编码实现。
实际上 DDD 的建设包括 战略设计 和 战术设计 两部分。
下面这些内容对没有参加过企业工作的同学来说会有些难理解,学习时可以跳过。
战略设计
从业务出发,建立领域模型,统一限界上下文。
设计时,需要先进行事件风暴(类似于头脑风暴),邀请领域专家、架构师、开发人员、测试人员、产品经理、项目经理等团队人员一起参加讨论。
描述个场景,大家在会议室里,搞一个大白板,参与者们将自己的想法和意见写在贴纸里并罗列到白板上,大家 先发散思维 进行讨论、记录。
主要讨论的内容是:系统会涉及哪些业务,哪个业务动作会触发另一个业务的什么动作,其间的输入是什么?输出是什么?
通过这类分析把所有的业务、业务行为、业务结果都罗列出来,拆分出领域模型中的事件、命令、实体等领域对象。然后梳理这些领域对象之间的关系,从不同维度进行聚类,形成聚合、聚合根、限界上下文等,这个过程就是 收敛。 限界上下文可以简单理解为微服务的边界,将其映射到代码模型,就完成了微服务的拆分。
💡 事件风暴实际上会利用常见的产品设计和用户体验分析方法,比如:
- 用例分析:对系统功能需求进行描述,以确定系统如何与外部参与者(即用户或其他系统)进行交互
- 场景分析:通过设定具体的情境或情景,来探讨用户如何在不同的环境下使用产品或系统
- 用户旅程分析:从用户的角度,描绘用户在使用产品或服务的过程中,从开始到结束的一系列步骤或行为
战术设计
从技术实现出发,将领域模型和代码模型进行映射
这个阶段就是完成代码落地,包括聚合、聚合根、实体、值对象等代码逻辑的设计与实现。
DDD 建模总结
结合上面的名词解析,我们回顾一下 DDD 建模的流程。
首先我们需要领域建模,此时会进行事件风暴,通过用例分析、场景分析等方式列出所有的业务行为与事件,找出产生这些行为的领域对象,包括实体与值对象。梳理这些领域对象之间的关系,从实体中找出聚合根,再根据聚合根的业务,找寻与其业务紧密关联其它实体与值对象,从而形成聚合。多个聚合之间根据业务相关性又可以划出限界上下文。
可以通过 "开公司" 的比喻来帮助大家理解 DDD。领域就像公司的行业,决定了公司所从事的核心业务;限界上下文是公司内部的各个部门,每个部门有独立的职责和规则;实体是公司中的员工,具有唯一标识和生命周期;值对象是员工的地址或电话等属性,只有值的意义,没有独立的身份;聚合是部门,由多个实体和值对象组成,聚合根(如部门经理)是部门的入口,确保部门内部的一致性;领域服务则是跨部门的职能服务,比如 HR 或 IT 服务,为各部门提供支持和协作。
DDD 的分层架构

相较于MVC多了一层。
1)用户接口层
也叫表示层或 Web 层,主要负责与外部(用户、API 等)的交互。它的主要职责是接收用户输入并返回系统的输出。表示层不包含业务逻辑,而是将用户的请求转发到应用层处理,并将处理结果返回给用户。
2)应用层
应用层主要用来协调领域层的逻辑和基础设施层的资源。应用层不包含业务规则或业务逻辑,但会调用领域层的服务进行服务编排与组合,来实现特定的业务。
如果有对其他服务的远程调用,也放在这层实现。除此之外,权限校验、事务、事件等操作也都可以放在这层进行实现。
3)领域层
领域层是整个架构的核心,包含了应用的业务逻辑、规则和策略。它定义了核心的领域模型,包括聚合根、实体、值对象、领域服务等。
领域层的目的是将业务需求转化为代码,并确保业务规则在应用中得以执行。该层的设计强调与业务领域的紧密耦合,是 DDD 中的重点。
4)基础设施层
基础设施层提供技术支持和持久化服务,采用依赖倒置设计,封装基础资源。负责与外部系统(如数据库、消息队列、缓存等)的交互。基础设施层的主要职责是实现应用层和领域层所需要的技术服务,如数据存储、邮件发送、日志记录等等。
依赖倒置设计实际上指的是各层对基础资源(如数据库)仅依赖其接口而不是具体的实现,假设后续替换基础资源(数据库),仅需替换具体实现,不需要修改各层依赖的代码。
三层架构到 DDD 四层架构的转化

主要改造点就是业务逻辑层的 Service,根据聚合拆分到应用层的应用服务与领域层的领域服务,部分业务逻辑还会以充血模型下沉到 Entity 中。
接着就是数据访问层的改造,根据依赖倒置原则,数据库的访问接口会被放到领域层中(因为属于行为),具体的访问实现则是在基础设施层内(为行为提供支持)。除此之外,第三方工具、Common、Config 等都放在基础设施层中。
也就是Service分成了两层,应用层与领域层。应用层用来协同领域层的相互调用,领域层中如果与实体相关的就放到实体里(充血模型),不相关的大部分主体代码在domain service。
DDD 代码架构
按照四层架构,我们可以建立 interfaces(用户接口层)、application(应用层)、domain(领域层)、infrastructure(基础设施层) 这 4 个包。

1.interface
该层主要负责与外部系统交互,包括用户界面(UI)、API 接口、请求的接收和响应的返回等。它作为领域层与外部世界的接口,确保领域逻辑的解耦。
存放的代码:
- 控制器(Controller):处理 HTTP 请求,负责路由和请求的转发。
- REST API 接口:定义暴露给外部系统的服务接口。
- 请求和响应对象:用于与外部系统交换数据。
2、application
该层负责协调多个领域对象的操作,完成应用级的任务。它充当领域层与用户接口层之间的桥梁,调用领域层中的业务逻辑,并将结果返回给用户接口层。应用层的职责是实现具体用例,而不包含业务规则。
存放的代码:
- 应用服务(Application Service):负责组织和协调领域对象,处理跨多个聚合的操作,通常表示应用中的具体功能,如 "下订单" 或"注册用户"。
3、domain
该层包含核心业务逻辑,它是系统的核心部分,负责模型的定义和业务规则的实现。领域层中的模型代表着业务概念,通常会包括聚合、实体和值对象。这个层不依赖于任何外部技术或框架,它专注于业务本身。
存放的代码:
- 聚合:一个聚合由多个实体和值对象构成,它们之间有着一致的业务规则,一般包名就代表一个聚合。
- 实体:具有唯一标识符(ID)的对象。
- 值对象:没有身份标识且是不可变的对象,通常用于表示某个概念的属性。
- 领域服务:当某个业务逻辑无法归属到某个实体或聚合时,使用领域服务来封装这些业务逻辑。
- 领域事件:表示领域中发生的某个重要事件,如 "订单已支付"。
- 仓储接口:定义资源访问的接口
- 持久化对象:PO(数据库查询逻辑不复杂时,可以省略)
4、infrastructure
该层提供技术支持,是所有其他层的基础设施。它包含数据库操作、消息队列、缓存、文件存储等第三方依赖。基础设施层实现了与外部系统的交互,但不包含业务逻辑。
存放的代码:
- 持久化:如使用 JPA 或 MyBatis 等技术实现数据库的访问。
- 外部系统集成:与外部服务或系统的通信,如调用文件存储。
- 工具类和基础设施组件:提供诸如日志、定时任务、邮件发送等功能。
项目重构实战
1.先划分领域,基于 MVC原有代码的model => mapper => service => controller 的顺序拆分,使用拖拽的方式。
2.将有关技术细节的基础设施代码放到infrastructre层,例如mapper,annotation,aop,exception等
3.重构model包,dto与vo放到interfaces层,entity与enums,constant放到domain层。重构数据访问层repository 包。
4.重构service层。这里先全放到application层,再进行下沉。下沉原则:1.将业务逻辑下沉到 领域服务或实体类 中,应用服务层需要调用领域服务或实体类来完成业务逻辑。2.如果某个方法需要调用其他应用服务(在单个领域内无法完成),那么该方法不能放到领域服务中,而是保留在应用服务中,因为原则上领域服务不应该调用应用服务。3.负责为接口层提供调用支持,因为原则上接口层只能调用应用服务层。
5.重构controller层。在interface中编写转换类保证接口的精简。
6.最后运行,保证代码兼容。
