DDD简介

概述

传统的数据驱动开发模式,View、Service、Dao这种三层分层模式,会很自然的写出过程式代码,这种开发方式中的对象只是数据载体,而没有行为,是一种贫血对象模型。以数据为中心,以数据库ER图为设计驱动,分层架构在这种开发模式下可以认为是数据处理和实现的过程。

Domain Driven Design,领域驱动设计,来源于Eric Evans的书《Domain-Driven Design --Tackling Complexity in the Heart of Software》。它倡导通过建立领域模型来驱动软件的设计,从业务知识出发设计业务人员和技术人员都能理解的模型。

思想

Alberto(Event Storming的提出者)认为,DDD是一种在面向高度复杂的软件系统时,关于如何去建模的方法论。其关键点是根据系统的复杂程度,建立合适的模型。在一个系统中,没有一个人能完全掌握系统的全貌。在多人参与的系统中,DDD正是可以通过在不同角色之间进行协作,使参与者达成统一认知,对齐系统设计与程序实际所服务的业务领域。

图片源:https://insights.thoughtworks.cn/ddd-architecture-design/

统一语言是领域驱动设计的关键概念之一。领域建模的核心难点就在于业务和技术人员使用不同的概念(词汇)来描述对软件的理解。领域模型就是跨越业务领域作为问题空间和软件解决方案空间的桥梁。

概念

几种域

  • 领域:领域就是范围。范围的重点是边界。领域的核心思想是将问题逐级细分来减低业务和系统的复杂度;
  • 子域:领域进一步划分,得到子领域,即子域。处理高度复杂领域的设计思想,它试图分离技术实现的复杂性;

领域划分过程中,会不断划分子域,子域按重要程度会被划分成三类:

  • 核心域:核心业务服务
  • 通用域:中间件服务或第三方服务;
  • 支撑域:企业公共服务;

限界上下文

Bound Context,BC。

领域模型集合之间由于业务的相关性可能形成松散的边界,这些边界就是分解复杂、大型问题为局部、小型问题的契机。通过辨析模型的相关性,找到边界就能为软件模块的划分、服务的划分提供指导。

在领域驱动中,识别出来的边界被称为限界上下文。

识别上下文不是一件容易的事情,可以从三个方面综合入手:

  • 领域知识
  • 团队合作
  • 技术实现

集成上下文的手段:开放领域服务接口、开放HTTP服务、消息发布-订阅机制。

限界上下文之间的映射关系:

  • Partnership:合作关系,两个上下文紧密合作的关系;
  • Shared Kernel:共享内核,两个上下文依赖部分共享的模型;
  • Customer-Supplier Development:客户方-供应方开发,上下文之间有组织的上下游依赖;
  • Conformist:遵奉者,下游上下文只能盲目依赖上游上下文;
  • Anticorruption Layer:防腐层,一个上下文通过一些适配和转换与另一个上下文交互;
  • Open Host Service:开放主机服务,定义一种协议来让其他上下文来对本上下文进行访问;
  • Published Language:发布语言,通常与OHS一起使用,用于定义开放主机的协议;
  • Big Ball of Mud:大泥球,混杂在一起的上下文关系,边界不清晰;
  • Separate Way:另谋他路,两个完全没有任何联系的上下文。

聚合和聚合根

聚合是一组始终需要保持一致的业务对象,有助于保持业务对象的一致性。聚合类似于包,每个包里包含一类实体或行为,有助于分散系统复杂性,也是一种高层次的抽象,可简化对领域模型的理解。聚合是通过定义领域对象之间清晰的所属关系以及边界来实现领域模型的内聚,以此来避免形成错综复杂的、难以维护的对象关系网。

聚合根:Aggregate Root,AR,一个上下文内可能包含多个聚合,每个聚合都有一个根实体,叫做聚合根,一个聚合只有一个聚合根。

聚合根属于实体对象,它是领域对象中一个高度内聚的核心对象。聚合根具有全局的唯一标识,而实体只有在聚合内部有唯一的本地标识,值对象没有唯一标识。若一个聚合仅有一个实体,那这个实体就是聚合根;但要有多个实体,就得思考聚合内哪个对象有独立存在的意义且可以和外部领域直接进行交互。

在定义聚合时,应遵守不变形约束法则:

  • 聚合边界内必须具有哪些信息,如果没有这些信息就不能称为一个有效的聚合;
  • 聚合内的某些对象的状态必须满足某个业务规则:
  • 一个聚合只有一个聚合根,聚合根是可以独立存在的,聚合中其他实体或值对象依赖与聚合根;
  • 只有聚合根才能被外部访问到,聚合根维护聚合的内部一致性。

实体和值对象

实体:Entity,当一个对象由其标识(而不是属性)区分时,这种对象称为实体。

值对象:Value Object,当一个对象用于对事物进行描述,而没有唯一标识时,则被称作值对象。

两个容易混淆的概念,对应编码时的Domain或Entity。

区别

实体 值对象
有生命周期 起描述作用
有唯一标识 无唯一标识
通过ID判断相等 实现equals方法
CRUD/持久化 即时创建用完就扔
可变 不可变

领域服务

Domain Service,一些重要的领域行为或操作,而并不是具体的事物,不太适合建模为实体对象或值对象。这些操作往往又会涉及到多个领域对象的操作,它们只负责来协调这些领域对象完成操作而已,可归类为领域服务。它实现全部业务逻辑并且通过各种校验手段保证业务的正确性,同时也能避免在应用层出现领域逻辑。领域服务有点facade意思。

工厂

DDD语境下,工厂(Factory)是一种封装思想的体现。引入原因:有时创建一个领域对象是一件相对比较复杂的事情,而不是简单的new操作。工厂的作用是隐藏创建对象的细节,这样就可以不会让领域层的业务逻辑泄露到应用层,同时也减轻应用层负担,它只要简单调用领域工厂来创建出期望的对象即可。事实上大部分情况下,领域对象的创建都不会相对太复杂,故仅需使用简单的构造函数创建对象就可以。

仓储

Repository,资源仓储封装基础设施来提供查询和持久化聚合操作。这样能够让我们始终关注在模型层面,把对象的存储和访问都委托给资源库来完成。它不是数据库的封装,而是领域层与基础设施之间的桥梁。DDD关心的是领域内的模型,而不是数据库的操作。

Facade

适配层,亦称防腐层。在一个上下文中,有时需要对外部上下文进行访问,通常会引入Facade层的概念来对外部上下文的访问进行一次转义。

考虑引入Facade层的几种情况:

  • 需要将外部上下文中的模型翻译成本上下文理解的模型;
  • 不同上下文之间的团队协作关系,如果是供奉者关系,建议引入防腐层,避免外部上下文变化对本上下文的侵蚀;
  • 该访问本上下文使用广泛,为了避免改动影响范围过大。

如果内部多个上下文对外部上下文需要访问,那么可以考虑将其放到通用上下文中。

两种模型

贫血模型适合简单场景,充血模型更符合DDD理念。选择哪种模型取决于项目的复杂性和团队的理解能力。

贫血模型

指领域对象里只有get和set方法,仅包含状态(属性),不包含行为(方法),业务逻辑通常在服务类中实现。

优点:

  • 简单易懂,代码结构清晰;
  • 业务逻辑与数据分离,便于维护和测试。

缺点:

  • 逻辑分散,可能导致服务类变得庞大且复杂;
  • 不易理解业务规则,因为它们不在模型中体现。

充血模型

指领域对象不仅包含属性,还封装与这些属性相关的业务逻辑。业务逻辑直接在领域对象中实现。

优点:

  • 业务逻辑紧密结合数据,易于理解和维护;
  • 领域模型更能反映真实世界的业务规则,便于团队沟通。

缺点:

  • 可能导致对象变得复杂,增加学习成本;
  • 在某些情况下,领域对象可能会变得庞大,难以维护。

事件风暴

从业务知识中提取出领域模型的方法:

  • 事件风暴
  • 彩色建模:建模方法,通过颜色区分不同领域模型的特点,来澄清领域模型的职责。

Event Storming,一种流行的软件建模方法,利用研讨会(workshop,也可译为工作坊,比较类似于头脑风暴)方式,快速发现软件特定领域中所发生事物的作法;引导业务人员和技术人员共同创作领域模型,以业务事件为线索,探索系统中可能的领域模型。

可供使用的在线工具BeeArt

延展阅读:事件风暴

四重边界

DDD通过规划四重边界,把领域知识进行合理的固化和分层。业务有核心领域和支持域、业务域中又拆分成多个限界上下文(BC),一个BC中又根据领域知识核心与否进行分层,领域层中按照多个业务(子域)的强相关性进行聚合成一个子域:

  • 第一重边界:确定项目的愿景与目标,确定问题空间,确定核心子领域、通用子领域(多个子领域可以复用)、支撑子领域(额外功能,如数据统计、导出报表);
  • 第二重边界:解决方案空间里的限界上下文就是一道进程隔离层面的物理边界;
  • 第三重边界:每个限界上下文内,使用分层架构划分为:接口层、领域层、应用层、基础设施层之间的最小隔离;
  • 第四重边界:领域层里为了保证各个领域的完整性和一致性,引入聚合的设计作为隔离领域模型的最小单元。

架构

分层架构

DDD分层架构中的依赖原则:每层只能与位于下方的层发生耦合,类似于网络的7层或TCP/IP的4层模型架构,每一层各司其职,并且只关心向下一层的实现,不出现各层耦合。

DDD分层架构从上到下包含四层:

  • 用户接入层:用户接口层,处理用户接入的数据结构,如RESTful API,或事件;
  • 应用层:处理用户的业务操作逻辑,也就是用例,它和用户的使用场景相关;
  • 领域层:处理通用的领域逻辑,也就是较为专业的业务逻辑,如订单价格计算;
  • 基础设施层:用来和基础设施适配,如连接数据库,操作Redis等。

六边形架构

Alistair Cockburn提出六边形架构,将应用分为内六边形和外六边形两层,内六边形实现应用的核心业务逻辑。外六边形完成外部应用,基础资源等的交互和访问,对于与不同的外部系统交互,由外六边形的适配器负责协议转换,保证内六边形业务逻辑的干净。

适配有两类:

  • 主动适配:指来自于UI、命令行等输入型命令,如Controller就是⼀种端口,端口的具体实现就是应用逻辑自身。端口和具体实现都在应用系统的内部。
  • 被动适配:指访问存储设备,外部服务等。每种访问就是⼀种端口,具体实现是各个具体的中间件。端口在整个应用系统内部,具体实现在系统外部。

每⼀种输入和输出都是⼀个端口,每个端口都有具体的实现逻辑,因此整个应用系统的架构就是⼀些列的端口+适配逻辑组成,架构图就是⼀个多边形形状。有几个端口需要根据应用系统的具体情况⽽定。

特点:

  • 外层依赖内层使得依赖更合理。端口就是接口,依赖接口编程。借此保证应用和实现细节之间的隔离;
  • 可测试性更好。

洋葱架构

Jeffrey Palermo提出的洋葱架构针对六边形架构更进⼀步把内层的业务逻辑分为DDD概念的应⽤服务层、领域服务层和领域模型层。

洋葱架构根据依赖原则,定义各层的依赖关系,越往里依赖程度越低,代码级别越高,越是核心能力。外圆代码依赖只能指向内圆,内圆不需要知道外圆的情况,都体现高内聚低耦合特性

特点:

  • 围绕独立的领域模型构建应用
  • 内层定义接口,外层实现接口
  • 依赖的方向指向圆心(洋葱架构提倡不破坏耦合方向的依赖都是合理的,外层可以依赖直接内层,也可以依赖更里面的层)
  • 所有的应用代码可以独立于基础设施编译和运行

分层协作

DDD各层的主要职责和分工协作示意图:

facade接口用于封装应用服务,适配不同前端需要的字段,提供不同要求的服务接口适配。

微服务之间通过消息队列实现解耦。

拓展

Event Sourcing

事件回溯,ES,主要特点是不保存对象的最新状态,而保存对象产生的所有事件,通过事件回溯得到对象最新的状态。通常是在每次对象参与完一个业务动作后,把对象的最新状态持久化到数据库中,即用数据库来反映对象当前最新状态。

一种存储状态变化的方式,所有状态的变化都以事件的形式保存,而不是直接保存当前状态。系统的状态可以通过重放这些事件来恢复。将业务操作记录为一系列事件,确保数据的不可变性和可追溯性。这在需要审计和历史回溯的场景中非常有用。

DDD为Event Sourcing和CQRS提供理论基础。领域模型可以被Event Sourcing和CQRS驱动,从而形成更好的设计。

EDA

Event Driven Architecture,事件驱动架构。

领域事件

DDD+ES的一种架构设计思想。领域事件,见名知意,发生在领域中的一些事件。将领域中发生的活动建模成一系列的离散事件,每个事件都用领域对象来表示,领域事件是领域模型的组成部分。

领域事件有产生、存储、分发和使用等操作,领域事件可由本地BC消费,也可由远程BC消费:

CQRS

Command and Query Responsibility Segregation,命令查询责任分离,Greg Young最早提出,架构图:

一种架构模式,将系统的命令(修改数据的操作)和查询(获取数据的操作)分开处理。这种分离允许在不同的模型和技术上优化命令和查询的处理。命令和查询分离使得开发者可以更好地把握对象细节,更好地理解哪些操作会改变系统的状态。

CQRS架构本身只是一个读写分离的思想,实现方式多种多样:

  • 数据库读写分离;
  • 底层存储不分离,但上层逻辑代码分离;
  • 系统底层存储分离,Command端采用Event Sourcing的技术,在EventStore中存储事件;Query端存储对象的最新状态,用于提供查询支持。

CQRS常与Event Sourcing结合使用,因为Event Sourcing提供的事件可以很自然地支持命令和查询的分离。

CRUD的缺陷,也就是引入CQRS的理由举例:

  • 把多条记录合并为一条;
  • 把不同地方的记录整合为一条虚拟记录。

适用场景:

  • 应用的写模型和读模型差别比较大时;
  • 需要对系统的查询和写入性能分开进行优化时,尤其是读写比非常高的系统,分离读写是必须的;
  • 系统需同时满足高并发的读、写时:CQRS架构可在Command端做到最大化的写,Query端容易提供可扩展的读模型;
  • 实践DDD:CQRS架构可让领域模型不受任何ORM框架带来的对象和数据库的阻抗失衡的影响。

实战

解耦合

业务拆分

微服务

用DDD的思想去指导微服务的实践。

中台设计

参考

相关推荐
J老熊7 小时前
JavaFX:简介、使用场景、常见问题及对比其他框架分析
java·开发语言·后端·面试·系统架构·软件工程
架构师Wu老七1 天前
【软考】系统架构设计师-信息系统基础
系统架构·软考·系统架构设计师·信息系统基础
程序员JerrySUN1 天前
熟悉的 Docker,陌生的 Podman
linux·docker·容器·系统架构·podman
张彦峰ZYF3 天前
DDD领域应用理论实践分析回顾
分布式·架构·系统架构·软件工程
爪哇学长4 天前
JavaFX 与其他图形库的详细比较:现代架构与性能优势
java·架构·系统架构
J老熊5 天前
RabbitMQ 在 Java 和 Spring Boot 中的应用详解
java·开发语言·spring boot·后端·系统架构·rabbitmq·java-rabbitmq
架构师Wu老七5 天前
【软考】系统架构设计师-计算机系统基础(4):计算机网络
计算机网络·系统架构·软考·系统架构设计师
银帅183350309716 天前
系统架构设计师论文
系统架构·论文笔记
架构师Wu老七6 天前
【软考】系统架构设计师-计算机系统基础(2):操作系统
系统架构·操作系统·软考·系统架构设计师
银帅183350309716 天前
2012年下半年试题一:论基于架构的软件设计方法及应用
架构·系统架构·论文笔记