文章目录
- 1 概念
- 1.1 名词概念
- 1 领域
- 2 限界上下文
- 3 上下文映射
- 4 应用服务
- 5 通用语言
- 6 实体
- 7 值对象
- 8 聚合
- 9 聚合根
- 10 领域模型
- 11 领域服务
- 12 领域事件
- 13 贫血模型
- 14 充血模型
- 15 防腐层
- 16 仓库
- 17 工厂
- 1.2 行为概念
- 1 战略设计
- 2 战术设计
- 3 问题空间
- 4 解决空间
- 5 事件风暴
- 2 DDD框架模式
- 2.1 四层架构优化版
- 2.2 整洁架构(洋葱架构)
- 2.3 CQRS架构(命令查询隔离架构)
- 2.4 六边形架构(端口适配器架构)
DDD领域驱动设计,DDD是为了解决快速变化、复杂系统的设计问题的。
DDD(Domain-Driven Design,领域驱动设计)是一种软件开发方法,它强调软件系统设计应该以问题领域为中心,而不是技术实现为主导。DDD通过一系列手段如统一语言、业务抽象、领域划分和领域建模等来控制软件复杂度,主要用来指导如何解耦业务系统、划分业务模块、定义业务领域模型及其交互。
MVC的开发模式:是数据驱动,从dao层、service、controller,自低向上的思想,关注数据。
DDD的开发模式:是领域驱动,自顶向下,关注业务活动。
1 概念
DDD领域驱动模型,是一种处理高度复杂领域的设计思想,不是一种架构,而是一种架构设计方法论,是一种设计模式。
图1(图片引自参考)
可以从上文图片中看出,DDD领域驱动模型的概念可以分为两类,一类是名词概念,另一类是行为概念。
名词概念如下:
1.1 名词概念
图1从外到内,名词解释如下
1 领域
DDD领域驱动设计,核心就是领域。从广义上讲,领域具体指一种特定的范围或区域。如图1,DDD中上下文的划分完后即为领域;图1从外向内,领域下又可划分为多个子域。
子域:在领域不断划分的过程中,领域会细分为不同的子域,子域可以根据自身重要性和功能属性划分为三类子域,它们分别是:核心域、通用域和支撑域。
- 核心域:它是业务成功的主要因素和公司的核心竞争力。比如电商系统,订单就是核心领域。
- 通用域:没有太多个性化的诉求,同时被多个子域使用的通用功能子域是通用域。比如通用的鉴权,用户管理。
- 支撑域:有一种功能子域是必需的,但既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域,就是支撑域。支付就是支撑子域。
讨论过程如何得出这些域,是战略设计要解决的。
2 限界上下文
应用层
限界上下文主要用来封装通用语言和领域对象。
限界上下文可以拆分为两个词,限界和上下文。限界:适用的对象一般是抽象事物,指不同事物的分界,指定某些事物的范围。上下文:同样的概念在不同的语义环境中可能含义不同,正确理解这些概念就要借助上下文。通用语言也有它的上下文环境。为了避免同样的概念或语义在不同的上下文环境中产生歧义,DDD 在战略设计上提出了“限界上下文”这个概念,用来确定语义所在的领域边界。限界上下文就是用来定义领域边界,以确保每个上下文含义在它特定的边界内都具有唯一的含义,领域模型则存在于这个边界之内,这个边界定义了模型的适用范围。
限界上下文是微服务设计和拆分的主要依据。在领域模型中,如果不考虑技术异构、团队沟通等其它外部因素,一个限界上下文理论上就可以设计为一个微服务。
限界上下文是业务概念的边界,是业务问题最小粒度的划分。在某个业务领域中会包含多个限界上下文,我们通过找出这些确定的限界上下文对系统进行解耦,要求每一个限界上下文其内部必须是紧密组织的、职责明确的、具有较高的内聚性。
3 上下文映射
应用层
上下文之间交互方式就是上下文映射,相对于系统里面这就是RPC,http等交互方式。
上面说道一个限界上下文理论上就可以设计为一个微服务,因此它们之间的映射可以看作是微服务间进行通信。
4 应用服务
应用层
应用服务作为总体协调者,先通过资源库获取到聚合根,然后调用聚合根或者领域服务中的业务方法,最后再次调用资源库保存聚合根。
作用:
- 除了同步方法调用外,还可以发布或者订阅领域事件,权限校验、事务控制,一个事务对应一个聚合根。
- 应用层方法主要执行服务编排等轻量级逻辑,尤其针对跨多个领域的业务场景,效果明显。
- 参数校验,简单的crud,可直接调用仓库接口
5 通用语言
应用层
DDD的主要参与者:领域专家+开发人员。领域专家擅长某个领域的知识,专注于交付的业务价值。而开发人员则注重于技术实现,总是想着类、接口、方法、设计模式、架构等。这也就导致了团队交流的困难性。因此找到双方的通用语言是解决该问题的有效途径。
通用语言定义上下文含义。在事件风暴过程中,通过团队交流达成共识的,能够简单、清晰、准确描述业务涵义和规则的语言就是通用语言。
通用语言包含术语和用例场景,并且能够直接反映在代码中。通用语言中的名词可以给领域对象命名,如商品、订单等,对应实体对象;而动词则表示一个动作或事件,如商品已下单、订单已付款等,对应领域事件或者命令。
6 实体
领域层
实体和值对象会形成聚合,因此先介绍下实体的概念。
每个聚合一般是在一个事务中操作,一般都有持久性操作。聚合中,跟实体的生命周期决定了聚合整体的生命周期,这里与UML类图的合成关系类似,可以参考超链接中的博文。比如用户实体对象就是一个实体,相对于6-值对象来说,更具有复杂性。
7 值对象
领域层
描述了领域中的一件东西,将不同的相关属性组合成了一个概念整体,当度量和描述改变时,可以用另外一个值对象予以替换,属性判等,固定不变。类似于具有多个字段的对象,比如下单的地址。
当你决定一个领域概念是否是一个值对象时,需考虑它是否拥有以下特征:
- 度量或者描述了领域中的一件东西
- 可作为不变量
- 将不同的相关的属性组合成一个概念整体(Conceptual Whole)
- 当度量和描述改变时,可以用另一个值对象予以替换
- 可以和其他值对象进行相等性比较
- 不会对协作对象造成副作用
- 当你只关心某个对象的属性时,该对象便可作为一个值对象。为其添加有意义的属性,并赋予它相应的行为。需要将值对象看成不变对象,不要给它任何身份标识,还应尽量避免像实体对象一样的复杂性。
值对象本质上就是一个集。该集合有若干用于描述目的、具有整体概念和不可修改的属性。该集合存在的意义是在领域建模的过程中,值对象可保证属性归类的清晰和概念的完整性,避免属性零碎。
8 聚合
领域层
实体和值对象会形成聚合,每个聚合一般是在一个事务中操作,一般都有持久性操作。聚合中,跟实体的生命周期决定了聚合整体的生命周期。
聚合的规范:
- 我们把一些关联性极强、生命周期一致的实体、值对象放到一个聚合里。
- 聚合是领域对象的显式分组,旨在支持领域模型的行为和不变性,同时充当一致性和事务性边界。
- 聚合在DDD分层架构里属于领域层,领域层包含了多个聚合,共同实现核心业务逻辑。跨多个实体的业务逻辑通过领域服务来实现,跨多个聚合的业务逻辑通过应用服务来实现。比如有的业务场景需要同一个聚合的 A 和 B 两个实体来共同完成,我们就可以将这段业务逻辑用领域服务来实现;而有的业务逻辑需要聚合 C和聚合 D 中的两个服务共同完成,这时你就可以用应用服务来组合这两个服务。
在DDD中,聚合也可以用来表示整体与部分的关系,但不再强调部分与整体的独立性。聚合是将相关联的领域对象进行显示分组,来表达整体的概念(也可以是单一的领域对象)。比如将表示订单与订单项的领域对象进行组合,来表达领域中订单这个整体概念。
图2(图片引自参考)
9 聚合根
领域层
聚合根(Aggreate Root, AR),顾名思意,就是聚合中最根本的实体,聚合根是主要的业务逻辑载体,DDD中所有的战术实现都围绕着聚合根展开。70%的场景下,一个聚合内都只有一个聚合根实体。比如订单和订单项聚合之后的聚合根就是订单。
聚合根的特征:
- 它作为实体本身,拥有实体的属性和业务行为,实现自身的业务逻辑。
- 它作为聚合的管理者,在聚合内部负责协调实体和值对象按照固定的业务规则协同完成共同的业务逻辑。
- 聚合根之间的引用通过ID完成。在聚合之间,它还是聚合对外的接口人,以聚合根 ID关联的方式接受外部任务和请求,在上下文内实现聚合之间的业务协同。也就是说,聚合之间通过聚合根 ID关联引用,如果需要访问其它聚合的实体,就要先访问聚合根,再导航到聚合内部实体,外部对象不能直接访问聚合内实体。这类似于数据库的主键,查询某条数据都要通过主键。
图3(图片引自参考)
10 领域模型
领域层
领域模型是对领域内的概念类或现实世界中对象的可视化表示。它专注于分析问题领域本身,发掘重要的业务领域概念,并建立业务领域概念之间的关系。
领域模型是描述业务用例实现的对象模型。它是对业务角色和业务实体之间应该如何联系和协作以执行业务的一种抽象。
领域模型分为领域对象和领域服务两大类,领域对象用于存储状态,领域服务用于改变领域对象的状态。
特点:
- 领域模型是对具有某个边界的领域的一个抽象,反映了领域内用户业务需求的本质;领域模型是有边界的,只反应了我们在领域内所关注的部分;
- 领域模型只反映业务,和任何技术实现无关;领域模型不仅能反映领域中的一些实体概念,如货物,书本,应聘记录,地址,等;还能反映领域中的一些过程概念,如资金转账,等;
- 领域模型确保了我们的软件的业务逻辑都在一个模型中,都在一个地方;这样对提高软件的可维护性,业务可理解性以及可重用性方面都有很好的帮助;
- 领域模型能够帮助开发人员相对平滑地将领域知识转化为软件构造;
- 领域模型贯穿软件分析、设计,以及开发的整个过程;领域专家、设计人员、开发人员通过领域模型进行交流,彼此共享知识与信息;因为大家面向的都是同一个模型,所以可以防止需求走样,可以让软件设计开发人员做出来的软件真正满足需求;
- 要建立正确的领域模型并不简单,需要领域专家、设计、开发人员积极沟通共同努力,然后才能使大家对领域的认识不断深入,从而不断细化和完善领域模型;
- 为了让领域模型看的见,我们需要用一些方法来表示它;图是表达领域模型最常用的方式,但不是唯一的表达方式,代码或文字描述也能表达领域模型;
- 领域模型是整个软件的核心,是软件中最有价值和最具竞争力的部分;设计足够精良且符合业务需求的领域模型能够更快速的响应需求变化;
11 领域服务
领域层
聚合根与领域服务负责封装实现业务逻辑。领域服务负责对聚合根进行调度和封装,同时可以对外提供各种形式的服务,对于不能直接通过聚合根完成的业务操作就需要通过领域服务。
前文7-聚合中提到,跨多个实体的业务逻辑通过领域服务来实现,跨多个聚合的业务逻辑通过应用服务来实现。比如说订单聚合要进行支付业务,那么支付聚合外要包一层领域服务,订单聚合外其实也有一层领域服务,更外层的应用服务协调调用订单与支付的领域服务 ,完成订单支付的流程。
在以下几种情况时,我们可以使用领域服务:
- 对于不能直接通过聚合根完成的业务操作就需要通过领域服务。
- 在DDD中,每个实体只能操作自己实体的变化,不能改另一个实体的状态。跨实体的状态变化需要抽象出一个领域服务,不能直接修改实体的状态,只能调用实体的业务方法。
- 以多个领域对象作为输入参数进行计算,结果产生一个值对象。
- 执行一个显著的业务操作
- 对领域对象进行转换
规范:
- 同限界上下文内的聚合之间的领域服务可直接调用
- 两个限界上下文的交互必须通过应用服务层抽离接口->适配层适配。
12 领域事件
领域层
聚合之间产生的业务协同使用领域事件的方式来完成,领域事件就是将上游聚合处理完成这个动作通过事件的方式进行抽象。
在DDD中有一个原则,一个业务用例对应一个事务,一个事务对应一个聚合根,也就是在一次事务中只能对一个聚合根操作。但在实际应用中,一个业务用例往往需要修改多个聚合根,而不同的聚合根可能在不同的限界上下文中,引入领域事件即不破坏DDD的一个事务只修改一个聚合根的原则,也能实现限界上下文之间的解耦。对于领域事件发布,在领域服务发布,在不使用领域服务的情况下,则由应用层在调用资源库持久化聚合根之后再发布领域事件。
一个事件可能当前限界上下文内也需要消费,即可能有多个限界上下文需要消费,一个事件对应多个消费者。
一个完整的领域事件 = 事件发布 + 事件存储 + 事件分发 + 事件处理。
- 事件发布:构建一个事件,需要唯一标识,然后发布;
- 事件存储:发布事件前需要存储,因为接收后的事建也会存储,可用于重试或对账等;就是每次执行一次具体的操作时,把行为记录下来,执行持久化。 非必须。
- 事件分发:服务内的应用服务或者领域服务直接发布给订阅者,服务外需要借助消息中间件,比如Kafka,RabbitMQ等,支持同步或者异步。
- 事件处理:先将事件存储,然后再处理。 非必须。
实现方案:发布订阅模式,分为跨上下文(kafka,RocketMq)和上下文内(spring事件,Guava Event Bus)的领域事件。
这里类似设计模式的观察者模式,通过消息事件进行订阅与监听。对于事务的处理,采用充分的解耦,使用消息队列的分布式事务,保证数据的最终一致性,如果消息消费失败,则重试或者其他补偿操作,比如用户注册后,发送短信和邮件,即通过领域事件进行解耦。
13 贫血模型
领域层
贫血模型具有一堆属性和set get方法,存在的问题就是通过pojo这个对象上看不出业务有哪些逻辑,一个pojo可能被多个模块调用,只能去上层各种各样的service来调用,这样以后当梳理这个实体有什么业务,只能一层一层去搜service,也就是贫血失忆症,不够面向对象。
日常项目中的对象类多是贫血模型。
14 充血模型
领域层
对实体的一些属性值的操作,都内聚在实体中,就是充血模型。比如ser用户有改密码,改手机号,修改登录失败次数等操作,都内聚在这个user实体中,每个实体的业务都是清晰的,充血模型的内存计算会多一些,内聚核心业务逻辑处理。实体就能看出有哪些业务存在。
15 防腐层
当某个功能模块需要依赖第三方系统提供的数据或者功能时,我们常用的策略就是直接使用外部系统的API、数据结构。这样存在的问题就是,因使用外部系统,而被外部系统的质量问题影响,从而“腐化”本身设计的问题。比如说外部系统返回的空null,自己系统不兼容,导致报错。
因此在两个系统之间加入一个中间层,隔离第三方系统的依赖,对第三方系统进行通讯转换和语义隔离,这个中间层,就是防腐层。类似设计模式的适配器模式。
防腐层作用:
- 使两方的系统解耦,隔离双方变更的影响,允许双方独立演进;
- 防腐层允许其它的外部系统能够在不改变现有系统的领域层的前提下,与该系统实现无缝集成,从而降低系统集成的开发工作量。
图4(图片引自参考)
16 仓库
应用层或领域层
负责提供聚合根或者持久化聚合根。仓库帮助我们持久化整个聚合的,存一个对象会把相关对象都存下来。从技术上讲,Repository和DAO所扮演的角色相似,不过DAO的设计初衷只是对数据库的一层很薄的封装,而Repository是更偏向于领域模型。
17 工厂
比如说创建一个实体,里面有五个值对象组成,每次创建的时候都得new一次,这里用工厂简化,工厂帮助我们创建聚合。这一方面可以享受到工厂模式本身的好处,另一方面,DDD中的Factory还具有将“聚合根的创建逻辑”显现出来的效果。Factory有两种实现方式:
1)直接在聚合根中实现Factory方法,常用于简单的创建过程
2)独立的Factory类,用于有一定复杂度的创建过程,或者创建逻辑不适合放在聚合根上
行为概念如下:
1.2 行为概念
DDD分为战略设计和战术设计,通过这两步进行设计。
1 战略设计
指的是领域名词、动词分析、提取领域模型。官方解释,在某个领域,核心围绕上下文的设计,主要关注上下文的划分、上下文映射的设计,通用语言的设计。
限界上下文是软件模块化的一种体现,战略设计有如下3个步骤:
- 需求分析,根据需求划分出初步的领域和限界上下文,以及上下文之间的关系;
- 领域分析,进一步分析每个上下文内部,抽取每个子域的领域概念,识别出哪些是实体,哪些是值对象;
- 领域建模,对实体、值对象进行关联和聚合,划分出聚合的范畴和聚合根;
2 战术设计
用领域模型指导设计及编码的实现。官方解释,核心关注上下文中的实体建模,定义值对象,实体等,更偏向开发细节。
战术设计有如下3个步骤:
- 编写核心业务逻辑,由领域模型驱动软件设计,通过代码来表现该领域模型,在实体和领域服务中实现核心业务逻辑;
- 为聚合根设计仓储,并思考实体或值对象的创建方式;
- 在工程中实践领域模型,并在实践中检验模型的合理性,倒推模型中不足的地方并重构。
3 问题空间
问题空间属于需求分析阶段,重点是明确这个系统要解决什么问题。问题空间将问题域提炼成更多可管理的子域,是针对于问题域而言的。自上而下进行分解。
4 解决空间
上文介绍了问题空间,有了问题域,就要解决,因此有相应的解决空间。
解决方案域属于系统设计阶段,针对识别出来的问题域,寻求合理的解决方案。
在领域驱动设计中,核心领域(Core Domain)与子领域(Sub Domain)属于问题域的范畴,限界上下文(Bounded Context)则属于解决方案域的范畴。
5 事件风暴
类似头脑风暴,软件开发人员和领域专家聚集在一起,完成领域模型设计。划分出微服务逻辑边界和物理边界,定义领域模型中的领域对象,以此指导微服务设计和开发。
领域模型设计具体包括领域分析和领域建模。
领域分析,是根据需求划分出初步的领域和限界上下文,以及上下文之间的关系;然后分析每个上下文内部,抽取每个子域的领域概念,识别出哪些是实体,哪些是值对象;
领域建模,就是对实体、值对象进行关联和聚合,划分出聚合的范畴和聚合根;
DDD需要进行领域分析和领域建模,除了事件风暴之外实现的方法有,领域故事讲述,四色建模法,用例法等。
事件风暴是建立领域模型的主要方法,但是在 DDD 领域建模和系统建设过程中,有很多的参与者,包括领域专家、产品经理、项目经理、架构师、开发经理和测试经理等。对同样的领域知识,不同的参与角色可能会有不同的理解,那大家交流起来就会有障碍,怎么办呢?因此,在 DDD 中就出现了“通用语言”和“限界上下文”这两个重要的概念。
2 DDD框架模式
2.1 四层架构优化版
允许某层与任意下方层发生耦合,是我们工作中更多使用的是松散分层架构。依赖倒置原则是具体依赖于抽象,而不是抽象依赖于具体。
通过Java设计六大原则中的依赖倒置原则实现各层对基础资源的解耦:也就是低层服务(如基础设施层)应依赖高层组件(比如用户界面层、应用层和领域层)所提供接口。高层定义好仓库的接口,基础设施层实现各层定义好的仓库接口。
图5(图片引自参考)
2.2 整洁架构(洋葱架构)
在整洁架构里,同心圆代表应用软件的不同部分,从里到外依次是领域模型、领域服务、应用服务和最外围的容易变化的内容,比如用户界面和基础设施。
整洁架构最主要的原则是依赖原则,它定义了各层的依赖关系,越往里依赖越低,代码级别越高,越是核心能力。外圆代码依赖只能指向内圆,内圆不需要知道外圆的任何情况。
图6(图片引自参考)
2.3 CQRS架构(命令查询隔离架构)
CQRS — Command Query Responsibility Segregation,故名思义是读写分离,就是将 command 与 query 分离的一种模式。
Command :命令则是对会引起数据发生变化操作的总称,即我们常说的新增,更新,删除这些操作,都是命令。
Query:查询则和字面意思一样,即不会对数据产生变化的操作,只是按照某些条件查找数据。
Command 与 Query 对应的数据源可以公用一种数据源,也可以是互相独立的,即更新操作在一个数据源,而查询操作在另一个数据源上。
CQRS三种模式
(1)共享模型/共享存储:读写公用一种领域模型,读写模型公用一种。
(2)分离模型/共享存储:读写分别用不同的领域模型,读操作使用读领域模型,写操作使用写领域模型。
(3)分离模式/分离存储:也叫做事件源 (Event source) CQRS,使用领域事件保证读写数据的一致性。也就是当 command 系统完成数据更新的操作后,会通过领域事件的方式通知 query 系统。query 系统在接受到事件之后更新自己的数据源。
CQRS(读写操作分别使用不同的数据库)
图7(图片引自参考)
2.4 六边形架构(端口适配器架构)
六边形架构的核心理念是:应用是通过端口与外部进行交互的
下图的红圈内的核心业务逻辑(应用程序和领域模型)与外部资源(包括 APP、Web 应用以及数据库资源等)完全隔离,仅通过适配器进行交互。它解决了业务逻辑与用户界面的代码交错问题,很好地实现了前后端分离。六边形架构各层的依赖关系与整洁架构一样,都是由外向内依赖。六边形架构相对于洋葱架构多了防腐层。
参考:
DDD参考文章
https://blog.csdn.net/u010020088/article/details/143212173
https://blog.csdn.net/JennyXi2001/article/details/136757558
DCP:
https://blog.csdn.net/weixin_30650859/article/details/97586806