DDD学习与感悟——实现领域驱动设计学习笔记

前言

以前就听说过DDD,但是DDD作为一种思想,比较抽象;并且没有太多机会接触国内实际落地的有优秀案例,因此以前草草了解便放弃了。 我所在的团队是整个业务的技术底座团队,目前底座支持的业务线、业务场景、行业领域越趋多样复杂,随之带来的系统演进迭代也越来越频繁、系统纵深越来越复杂,而DDD思想正是应对大型复杂软件系统的设计的利器。因此下定决心学习一下DDD思想,并应用于系统的迭代重构之中。

有些书就是很难读,有些文章就是很难写,有些知识就是很难懂,有些技能就是很难学......真正能让你变强的东西,其核心困难是无法回避的,不下决心与之死磕,始终在周围打转,时间越长越焦虑。

领域

领域(domain)指的是一个特定的业务领域或问题空间。它是一个独立的、自治的业务单元。包含一系列的业务逻辑和规则。

领域模型

领域模型是用于描述业务领域的概念、规则和流程的模型。它包含了实体、值对象、聚合根、限界上下文等元素,他们共同描述了业务领域的本质和特征。

子域

子域(sub domain)是领域的一个子集,是一个更小的业务领域。它可以是独立的业务单元,也可以是领域的一部分。

通用领域

通用领域是领域之间共享的概念,也叫做领域间关联关系。通用领域不属于任何一个领域。但他服务于领域。

银行系统中,有账户管理和信用卡两个领域。账户和信用卡之间有关联关系。比如一个账号可以拥有多张信用卡;一张信用卡归属于某个账户。账户和信用卡之间的关联关系就称为通用领域,它既不属于账户领域,也不属于信用卡领域。注意,通用领域不不是一个"领域",只是一个概念,表示领域间的关联和共享。

限界上下文

限界上下文是领域的边界,包含了一组业务逻辑和规则。每个上下文内都是独立的语言、模型和规则,是DDD中非常重要的概念。

主要作用:将领域划分成多个职责、边界清晰的子域。在边界上下文内,所有的概念和概念模型都是一致的。但是在不同的上下文之间,概念和概念模型可能会有所不同。

比如在安全认证领域中,用户管理限界上下文内,principal指的是某一个用户名称;但是在设备管理上下文中,principal可能指的是一个设备名称。

如何识别限界上下文

识别限界上下文对于领域建模而言是非常关键的。很大程度上决定了我们的领域建模是否合理、架构是否合理。

我们需要结合我们的业务去划分限界上下文,可以采用"事件风暴"的方法。

事件风暴

什么是事件?事件就是发生了的事情,是一个结果。比如:用户支付了订单;用户移除了商品等。

通过事件风暴,我们可以识别出实体和聚合根,再根据这些事件、实体以及聚合根识别出限界上下文。

在事件风暴的过程中,我们会列出所有的事件,然后将它们分类,找出它们之间的关系。对于每个分类,我们可以识别出其中的实体和聚合根。

实体是在我们的业务中有明确存在的对象,比如订单、产品、用户等。聚合根是一组关联的实体,它们一起组成了一个完整的业务概念。聚合根是我们在领域建模中非常重要的概念,它可以帮助我们更好地组织和管理业务逻辑和规则。

通过识别实体和聚合根,我们可以更好地理解我们的业务,有助于我们进行领域建模和设计出更好的架构。

事件风暴的例子:

事件:

•订单被创建

•订单被取消

•订单被退款

•订单被支付

•商品被创建

•商品被修改

•商品被删除

•用户被创建

•用户被修改

•用户被删除

分类:

•订单:创建、取消、退款、支付

•商品:创建、修改、删除

•用户:创建、修改、删除

实体:

•订单

•商品

•用户

聚合根:

•订单聚合根

•商品聚合根

•用户聚合根

限界上下文:

•订单管理上下文

•商品管理上下文

•用户管理上下文

通过事件风暴的过程,我们可以识别出实体、聚合根和限界上下文,有助于我们更好地理解业务和进行领域建模。

上下文映射图

上下文映射图指的的不同上下文之间的关联关系和集成关系。在实践当中,上下文映射图通常有上游和下游。上游称为服务提供者,下游则称为尊奉者或者服务消费者。

在上下文映射图中,上游是开放主机服务,定义了消息发布的方式和消息的语言,比如提供者通过REST方式提供服务,消息语言是JSON格式。而下游通常需要防腐层,将消息转换为内部上下文中的概念。下图举例:
# 实体

实体是领域模型中的重要元素。它是具有唯一标识符的对象。拥有自己的属性和行为。是可以独立存在的,通常情况下,实体具备完整的生命周期。包括创建、修改和删除等过程。

比如SaaS平台中的订单,有创建、支付、过期等状态。

值对象

值对象是用于度量或者描述一个东西,它将不相关的属性组合在一起表示一个概念整体。其本身具备不变性。

换句话来说就是:值对象是对实体的描述。

如何区分值对象和实体?
实体和值对象都拥有自己的属性和行为。不同的是,实体具备完整的生命周期。
比如订单实体有下单行为,下单之后,订单状态变更为创建状态。
但是值对象具备不变性,也就是说值对象的行为不会改变值对象的属性。
另外,实体具有唯一标识符,但是值对象没有唯一标识符号。
同一个概念在不同的限界上下文内可能是实体,也可能是值对象。
比如:用户管理上下文中,用户地址是一个子域的实体,可以拥有状态;
但是在订单管理上下文中,下单时的送货地址是不变的,因此在这里它是一个值对象。

最小化集成

在一个业务领域中,通常会有多个限界上下文。上游的概念传入下游时,使用值对象传递是最合适的。

值对象的标准类型

所谓标准类型就是明确值对象属性的范围。因为值对象本身是具备不变性的,属性值是明确的。

在JAVA中,可以使用枚举来实现标准类型。

ini 复制代码
举例:比如SKU金额是500美元;那么它的值对象应该有两个属性:amount, currency;
单独的500不能表示

领域服务

什么是领域服务?

当某个业务操作过程或者对象的转换不是某一个实体或者值对象的职责时,此时我们需要将这个操作放在一个独立的接口中,即领域服务。

注意,我们所说的SOA服务、应用服务等和领域服务不是一个概念。千万不要混淆。

什么情况下需要使用领域服务?

1、需要执行一个显著的业务操作过程;

2、需要转换对象;

3、需要将多个领域对象作为输入,然后输出一个新的值对象。

领域服务不是银弹,如果我们过度依赖领域服务,则会造成贫血领域模型。

领域事件

领域事件(Domain Event)是在领域中发生的一些有意义的、重要的事情,比如订单被创建、用户注册成功等。领域事件和领域模型是密切相关的,是领域模型中的一部分。

领域事件是领域模型中的一个非常重要的概念,是实现领域驱动设计的关键。

领域事件的作用

1.使领域模型更加丰富和完整

通过领域事件,我们可以将领域模型中的概念和业务行为更加丰富和完整地描述出来,有助于我们更好地理解业务和进行领域建模。

1.解耦领域模型和应用程序

应用程序和领域模型的耦合是DDD中需要避免的问题之一。通过领域事件,我们可以将应用程序和领域模型解耦,使得领域模型更加独立和自治。

1.促进领域模型的演化和变化

领域事件可以帮助我们更好地理解业务和领域模型的变化。通过领域事件,我们可以更加方便地进行领域模型的演化和变化。

领域事件的分类

在领域驱动设计中,领域事件可以分为两类:领域事件和集成事件。领域事件通常是在领域内部,而集成事件通常是在不同的系统之间。

1.领域事件

领域事件是发生在领域内部的一些重要事件,比如订单被创建、用户注册成功等。领域事件通常是在领域模型中定义的,是一个领域模型中的一部分。

领域事件通常是由领域对象或领域服务触发的,是领域内部的一些重要行为。领域事件通常会包含一些重要的信息,比如事件发生的时间、事件的类型、事件的来源等。

1.集成事件

集成事件发生在不同的系统之间,通常是在系统之间进行数据交换或者信息传递的过程中。集成事件通常是由消息队列、消息总线等技术来实现的。

集成事件通常包含一些重要的信息,比如事件的来源、事件的目标、事件发生的时间、事件的类型等。

领域事件的处理方式

领域事件通常会被发送到一个或多个订阅者,订阅者可以是本地的对象、远程的服务,甚至是其他的系统。订阅者可以根据自己的需求来处理领域事件,比如更新自己的状态、生成报表等。

领域事件的处理方式通常有两种:同步处理和异步处理。

1.同步处理

同步处理是指在事件发生后立即进行处理,等处理完成后再继续执行后续操作。同步处理通常适用于处理时间较短、业务逻辑较简单的情况。

1.异步处理

异步处理是指在事件发生后不立即进行处理,而是把事件放到消息队列或消息总线中,等待后续

模块

在DDD中,模块表示一个命名的容器,用于存放一组内聚的类。模块是实现高内聚低耦合的非常重要的部分。

在不同的语言中,模块表示不同的含义。比如在JAVA语言中,模块就是包,在C语言中,模块就是命名空间。

模块的命名

模块的命名非常重要,通常情况下我们采用通用语言来命名模块,通过模块名就可以反映出他们的领域中的概念。

比如,在IDaaS中,租户、用户就是通用语言,那么我们的模块命名应该为:com.xx.tenant;com.xx.user。通过模块名就可以反映出他们在领域中的概念。

聚合

在DDD中,聚合是指由一组对象组合而成。这些对象组合在一起形成一个完成的业务概念和逻辑。聚合是保护数据完整性的一种机制。

聚合和聚合根的区别

他们的区别就好比树根和树的区别。聚合根是聚合的唯一入口。

举个例子:
在电商系统中,订单实体和商品实体形成聚合。订单实体拥有订单ID、订单创建时间、收货地址等属性;
商品实体有商品ID、商品名称、库存等属性;在订单领域中,订单实体是这个聚合的唯一入口。

遵循聚合设计原则

原则如下:

1、聚合应该是一个封闭的单元,对外部世界只提供必要的方法,以确保数据的完整性和唯一性;

2、聚合内部的对象应该有明确的边界和关系,避免对象之间的混淆和耦合;

3、聚合应该定义明确的生命周期,包括创建、修改和删除等操作,以确保数据的正确性和一致性;

遵循聚合设计原则的好处:可以使得系统更健壮、可维护和可扩展,也能够提高开发效率和用户体验。

最佳实践

"迪米特"法则:最小开放原则

"告诉而非询问"法则

举个例子:下单过程中,需要扣库存。此时应该是告诉商品实体扣库存,
如果商品实体扣库存失败,则抛出业务异常,由订单实体处理异常告诉用户下单失败;
而不是订单实体询问商品实体库存是否足够,如果足够则扣库存,否则下单失败。

"告诉而非询问"法则很好的体现了实体之间的边界和关联关系,比"迪米特法则"更加灵活实用。

资源库

在DDD中,资源库(Repository)是持久化聚合根的一种机制,它是领域层的一个概念。负责将聚合根持久化到数据库,也负责从数据库中读取聚合根到内存。负责管理聚合根的整个生命周期。

资源库与DAO的区别

资源库是面向领域对象的,而DAO是面向数据存储的,提供基本的增删改查功能。资源库在持久化聚合根时,需要依赖DAO,因此资源库是基于DAO之上的,它封装了DAO的基础功能,并提供了更高层次的抽象和封装。

终于弄懂了Repository和DAO的区别以及它们各自的作用了✌️

集成限界上下文

一个完整的业务领域通常会有多个限界上下文。上下文之间会相互依赖。

我们通过限界上下文映射图可以知道上下文之间的上下游管理关系。

集成的方式有多种:

1、通过REST API方式;

2、基于事件发布;

3、基于RPC远程调用

应用程序

应用程序通过用户界面对外展示领域模型的核心概念,允许用户通过用户界面执行各种操作。这里的应用程序通常指的是应用层。

应用服务

用户界面通过应用服务来管理协调任务和管理事务,并进行一些必要的安全授权。

应用服务VS领域服务

注意,应用服务于领域服务完全不是一个概念。通常一个业务流程会涉及多个领域,应用服务通过对多个领域服务进行聚合完成一个完整的业务流程操作。其本身是一个很轻量级的一层。通常会通过DTO(数据传输对象)返回数据给到用户界面。

而领域服务才是真正执行业务操作逻辑的服务,因为在DDD中,实体拥有自身的属性和行为,且具备完整的生命周期和事件发布的能力。

实现DDD的架构

六边形架构

六边形架构是一种基于端口和适配器的架构模式。用于构建可扩展、可维护、可测试的一种架构模式。

六边形架构的本质思想是将应用程序分成三层:领域层、应用层和基础设施层。其中,领域层是核心,实现业务领域的核心业务逻辑,应用层通过适配器协调领域层、基础设施层以及外部界面的交互;基础设施层则负责与外部系统进行交互。比如数据库、缓存服务等。

注意,在六边形架构中提到的领域层、应用层以及基础设施层是一种基于DDD思想划分的逻辑层,并不是真正的工程层级划分。比如在领域层中,实体需要发布事件,比如是MQ事件,则需要依赖基础设施层。

面向服务架构(SOA)

SOA架构是面向服务架构。它将业务功能划分为一个个独立的服务,服务之间通过HTTP或者RPC远程调用通信。SOA架构模式的目标是实现一个可维护、可扩展、高内聚低耦合的系统架构。在目标上与DDD思想中的六边形架构有一定的重合之处。但和六边形架构不是一个概念。

SOA VS 六边形架构

SOA架构和六边形架构不是一个概念。

SOA架构是面向服务架构模式,面向业务功能的架构实现。

而六边形架构是基于DDD思想的、面向业务领域的架构实现。

因此它们的维度和概念完全不一样,但是实现目标有一定的重合之处。都是为了能够实现可扩展、可维护、可测试的系统目标。

所以,我们可以使用SOA架构模式来实现六边形架构思想,从而落地DDD。

SOA VS 微服务架构

SOA架构和微服务架构的区别在于它们的划分粒度不一样。

SOA架构通常是以业务功能维度来划分服务,比如订单管理、商品管理、库存管理等维度。

微服务架构通常是以业务能力维度来划分服务,粒度更细。比如订单管理中,拥有订单的创建、更新、删除等能力,我们可以将这些能力分别对应一个微服务,独立发展和维护。

CQRS架构

CQRS架构是一种读写分离架构模式。它强调将命令模式(写模型)和查询模式(读模型)进行分离,分别使用不同的技术来实现,在DDD,通过CQRS架构可以很好的实现业务领域模型。

写模型可以使用三层架构或者六边形架构实现,用于实现领域模型中复杂的业务逻辑;

读模型通常使用数据仓库或者缓存实现。

通过CQRS架构,可以有效解偶业务复杂性,并天然支持事件驱动,通过事件通信将写模型和读模型关联起来。

在实际业务中,需要结合自身业务情况来实现CQRS架构。

洋葱架构

洋葱架构也是一种软件架构模式,和六边形架构有点相似,但有区别。 洋葱架构强调将应用程序划分为多个层次,每个层次都有自己清晰的职责。通常情况下划分为领域层、应用层、基础设施层以及用户界面层。每一层都有自己清晰职责。

总结

以上是我对《实现领域驱动设计》一书的学习笔记和个人的一些见解思考。本书偏向于理论知识,篇幅层面战略设计偏少,战术设计偏多,这也是正常的,真正的战略设计都是需要结合实际的业务。这本书对于我而言,具有重要的DDD思想扫盲和我落地DDD具有重要的指导意义。值得深读。

相关推荐
许野平1 小时前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
58沈剑2 小时前
80后聊架构:架构设计中两个重要指标,延时与吞吐量(Latency vs Throughput) | 架构师之路...
架构
齐 飞2 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
LunarCod3 小时前
WorkFlow源码剖析——Communicator之TCPServer(中)
后端·workflow·c/c++·网络框架·源码剖析·高性能高并发
码农派大星。3 小时前
Spring Boot 配置文件
java·spring boot·后端
杜杜的man4 小时前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang
幼儿园老大*4 小时前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
llllinuuu4 小时前
Go语言结构体、方法与接口
开发语言·后端·golang
cookies_s_s4 小时前
Golang--协程和管道
开发语言·后端·golang
为什么这亚子4 小时前
九、Go语言快速入门之map
运维·开发语言·后端·算法·云原生·golang·云计算