概述
DDD核心知识体系有: 领域、子域、核心子域、通用子域、支撑子域、限界上下文、实体、值对象、聚合和聚合根、领域事件、领域服务、应用服务和分层架构等;
1.领域和子域
领域的基本概念
- 领域是从事一种专门活动或事业的范围、部类或部门;
- 领域具体指一种特定的范围或区域;
- 在研究和解决业务问题时, DDD会按照一定的规则对业务领域进行细分,当领域细分到一定的程度后,DDD会将问题范围限定在特定的边界内,在这个边界内建立领域模型,进而用代码实现该领域模型,解决相应的业务问题;
- 简言之,DDD的领域就是这个边界内要解决的业务问题域;
子域的分类和属性
根据子域自身的重要性和功能属性将它们划分为三类子域,分别是: 核心子域、通用子域和支撑子域;
- 在企业内决定产品或企业核心竞争力的功能子域是核心子域,它是让企业业务利商业模式成功的关键核心能力,是企业在面对竟争对手时所拥有的核心竞争力;
- 没有太多个性化的诉求,同时又会被多个子域重复使用的通用功能子域是通用子域;
- 必需存在,但它既不是决定产品或企业核心竞争力的功能,也不是被其他子域复用的通用功能,这类子域是支撑子域;
说明
战略方向和商业模式的不同最终会导致核心子域划分结果酌不同;
行业相同,但企业战略目标和定位不一祥,核心子域也就会不—样;
建议技术团队将核心子域的建设排在首位,对核心子域的建设,最好要有绝对的掌控能力和自主研发能力;
核心思想
DDD领域划分的核心思想就是将问题域逐级细分,采用分而治之的策略,将复杂问题简单化,从而降低业务理解和系统实现的复杂度;
2.限界上下文
通用语言用于定义上下文对象的含义,而限界上下文则用于定义领域边界,以确保每个上下文对象在它特定的边界内具有唯一的含义,在这个边界内,组合这些对象构建领域模型;
通用语言
- 在事件风暴过程中,通过团队交流达成共识的,能够简单、清晰、准确地描述业务含义和规则的语言就是通用语言;
- 解决交流障碍的问题,使得领域专家与项目团队以及项目团队内部成员之间,能够用共同的语言进行交流和协同合作,从而确保业务需求的正确表达和系统的正确实现;
- 通用语言贯穿DDD的整个设计过程。作为项目团队沟通和协商过程中形成的统一语
言,它通过将领域模型映射到代码模型,可以将这些名词术语直接反映到代码中;
设计过程中我们可以用一些表格来记录事件风暴和微服务设计过程中产生的领域对象及其属性。比如: 领域对象在DDD分层架构中的位置、实体的属性、领域对象之间的依赖关系以及代码对象与领域对象的映射关系等。可以指导软件开发人员准确无误地按照领域模型和设计文档完成微服务开发;
限界上下文
- 限界上下文就是在限定的上下文环境内,用来封装通用语言和领域对象。保证领域内的一些术语、领域对象等有一个确切的含义,没有语义二义性的一个业务边界;
- 定义限界上下文时通常会考虑领域业务职责单一这个因素。在确定了领域的职责边界后,会将所有与实现该领域职能相关的对象都放在同一个限界上下文边界内,而将所有与该领域职能无关的对象都排除在上下文边界之外。限界上下文就是这样一个强制边界,它可以保证领域职责的单一性和领域模型的纯洁性;
3.实体和值对象
在用户旅程分析或场景分析构建领域模型时,实体和值对象是偏业务领域的,主要体现为业务属性和业务行为。而当它们从领域模型映射到代码模型时,这些领域对象会变成代码对象,这时候的我们会更关注这些领域对象的依赖关系,关注如何一起按照聚合的业务规则实现业务逻辑。当这些领域对象持久化存储到数据库时,它们的名称和状态可能又会发生变化,此时我们需要将这些领域对象转换为持久化对象,完成数据的持久化;
实体
在DDD的领域模型中有这样一类对象,它们拥有唯一标识符,并且它们的标识符在历经各种状态变更后仍能保持一致。对这些对象而言,重要的不是属性,而是其延续性和标识,这种对象的延续性和标识会跨越甚至超出软件的生命周期。我们把领域模型中这样的领域对象称为实体;
A.实体的业务形态
- 在战略设计时,它是业务形态的业务对象,集多个业务属性、业务操作或行为于一体;
- 按照一定的业务规则将依存度高和业务关联紧密的多个实体对象和值对象进行聚类,形成聚合;
- 实体和值对象是组成领域模型的基础单元。
B.实体的代码形态
- 在代码模型中,实体的表现形式是实体类,这个类包含了实体的属性和方法。通过这些方法实现实体自身的业务行为和业务逻辑;
- DDD更强调面象对象的设计方法。这些实体类通常采用充血模型,与实体相关的所有业务逻辑都在实体类方法中实现,跨多个实体的领域逻辑则在领域服务中实现;
C.实体的运行形态
实体以领域对象(DO)的形式存在,每个实体对象都有唯一的ID;
D.实体的数据库形态
在领域模型映射到数据模型时,一个实体可能对应0个、 1个或者多个数据库持久化对象。大多数情况下实体与持久化对象是—对一。在某些场景中,有些实体只是暂驻内存的一个运行态实体,不需要持久化;
值对象
- 值对象是通过对象属性值来识别的对象。它将多个相关属性组合为—个概念整体,用于描述领域的某个特定方面,并且是—个没有标识符的对象;
- 值对象描述了领域中的某一个东西,这个东西是不可变的。它将不同的关联属性组合成了一个概念整体;
A.值对象的业务形态
值对象虽然也是若干个属性的集合,但它只有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑。值对象的属性集虽然在物理上独立出来了,但在逻辑上你仍然可以认为它是实体属性的一部分,用干描述实体的特征;
B.值对象的代码形态
值对象在代码中有两种形态:
- 如果值对象是单一属性,则直接定义为实体类的属性;
- 如果值对象是属性集合,则将它设计为值对象类,这个类将具有整体概念的多个属性归集到属性集合,这样的值对象没有ID,会被实体整体引用;
C.值对象的数据库形态
- DDD引人值对象是希望实现从“数据建模为中心”向“领域建模为中心”的转变,减少数据库表的数量和表与表之间复杂的依赖关系,尽可能简化数据库设计,提升数据库性能;
传统的数据建模大多是根据数据库范式设计的,每一个数据库表对应一个实体,每一个实体的属性值用单独的一列来存储,一个实体主表会对应N个从表。而值对象简化了数据库持久化方面的设计,它的数据库设计大多采用了非数据库范式,值对象的属性值和引用它的实体对象的属性值一般保存在同—个数据库实体表中;
D.值对象的优势和局限
- 值对象采用序列化大对象的方法简化了数据库设计,减少了实体表的数量,可以简单清晰地表达业务概念,降低了数据库设计的复杂度。但是会导致搜索值对象属性值时变得异常困难;
- 值对象的不可变性,确保了值对象永远都是正确的,在并发环境下不会被意外修改。所以在它同时被多个实体引用时,可以实现重用利共享,从而提高系统性能。一般建议将领域对象优先设计为值对象,而非实体;
实体和值对象的关系
- 实体和值对象都是微服务底层的最基础的领域对象,—起实现领域模型最基本的核心领域逻辑。值对象利实体在某些场景下可以互换;
- 很多值对象的数据可能来源于其他聚合,它们以数据冗余的方式完成不同领域中数据的流转和共享;
- 由于不同聚合中实体利值对象的这种关系,值对象还有一个重要的使用场景,那就是记录和生成业务的数据快照;
- 实体着重唯一性和延续性,不在意属性的变化,即使属雀全变了,它还是原来的那个它。值对象着重描述性,对属性的变化很敏感,属性变了,它就不是原来那个它了;
4.聚台和聚台根
在构建领域模型时,我们会根据用户旅程或场景分析中的一些业务操作利行为找出产生这些行为的实体或值对象,从这些实体对象中找出聚合根,进而将这些业务关联紧密的聚合根、实体和值对象组合在—起,构成聚合,再根据业务语义边界将多个聚合划定到同一个限界上下文中,在限界上下文内构建领域模型;
聚合
在传统数据模型中每一个实体都是对等的,在业务逻辑实现时,可以随意找到实体或数据库表完成数据修改,但这类操作在DDD的聚合内是不被允许的;
- 在领域模型中我们需要一个这样的组织,将这些紧密关联的个体对象聚集在一起,按照组织内统—的业务规则共同完成特定的业务功能,因此就有了聚合的概念;
- 从技术的角度,聚合是由业务和逻辑紧密关联的实体和值对象组合而成的。聚合内数据的修改必须由聚合根统一组织,以确保每次数据修改都是按照聚合内统一的业务规则来完成,聚合是数据修改和持久化的基本单元;
- 聚合在DDD分层架构里属干领域层,同一个微服务的领域层可以有多个聚合,每个聚合内有一个聚合根,多个实体,值对象和领域服务等领域对象。同一个限界上下文内的多个聚合,通过应用层组合在一起共同实现了领域模型的核心领域逻辑;
- 每一个聚合只有一个仓储完成聚合数据的持久化操作;
- 聚合内的实体以充血模型实现自身的业务逻辑,跨多个实体的领域逻辑通过领域服务来实现。跨多个聚合的业务逻辑的组合和编排,通过应用服务实现;
聚合根
在聚合根类的方法中,可以组织聚合内部的领域对象,完成跨多个实体的复杂业务逻辑。但是,在聚合的领域服务中,也可以完成跨多个实体的复杂领域逻辑;
- 首先,聚合根是实体。作为实体,它拥有实体的业务属性和业务行为,可以在聚合根实现自身的业务逻辑;
- 其次,它是聚合的管理者。在聚合内负责协调实体和值对象,按照固定的业务规则,协同完成聚合共同的业务逻辑;
- 最后,它还是聚合对外的联络人和接口人,聚合之间以聚合根ID关联的方式接受聚合的外部任务和请求。在限界上下文内实现聚合之间的业务协同,聚合外部对象不能直接通过对象引用的方式访问聚合内的对象;
聚合的设计步骤
- 第1步,采用事件风暴,梳理业务场景中发生的所有业务行为,找出产生这些行为的所有实体和值对象;
- 第2步,从众多实体中找出适合作为聚合对象管理者的根实体,也就是聚合根。判断一个实体是否是聚合根;
- 第3步,根据业务单一职责和高内聚原则,找出与聚合根关联的所有紧密依赖的实体和值对象,构建出一个包含聚合根(唯一)、多个实体和值对象的领域对象的集合,这个集合就是聚合;
- 第4步,在聚合内根据聚合根、实体和值对象的依赖关系,找出它们的引用和依赖关系;
- 第5步,多个聚合根据业务语义和上下文边界,划分到同一个限界上下文内,就完成了领域建模;
聚合的设计原则
- 第一条,在一致性边界内建模真正的不变条件;
聚合是用来封装真正的业务不变性,而不是简单地将对象组合在一起。聚合内有一套不变的业务规则,各实体利值对象按照统一的业务规则运行,保证聚合内对象数据的一致性。聚合是可以拆分为微服务的最小业务单元; - 第二条,设计小聚合;
小聚合设计可以降低由于业务过大,在业务变化时导致聚合重构的可能性。这样领域模型就更能适应业务的变化; - 第三条,通过唯一标识引用其他聚合;
聚合之间是通过引用聚合根ID的方式,而不是通过直接对象引用的方式; - 第四条,在边界之外使用最终一致性;
在聚合内采用数据强一致性,在聚合之间采用数据最终一致性。这是因为DDD强调在一次事务中,最多只能修攻一个聚合的数据。如果一次业务交易操作涉及了多个聚合数据的修改,那么应采用领域事件驱动机制。通过数据最终一致性异步更新所有聚合的数据,从而实现聚合解耦; - 第五条,通过应用层实现跨聚台的服务调用;
- 第六条,一个聚合只有一个仓储,统一由仓储来完成聚合数据的持久化;
5.领域事件
领域事件
- 领域事件是领域模型非常重要的—部分,用于表示领域中发生的事件。一个领域事件往往会导致进一步的业务操作,它在实现领域模型解耦的同时,还有助于形成完整的业务操作闭环;
- 领域事件采召事件驱动架构设计,可以切断领域模型之间的强依赖关系,在领域事件发布后,事件发布方不必关心订阅方的事件处理是否成功。这样就可以实现领域模型的解耦,维护领域模型的独立性。当领域模型映射到微服务时,领域事件就可以解耦微服务,这时微服务之间的数据就可以不再要求强一致性,而是基于最终一致性;
- 领域事件一般都会结合消息中间件和事件发布订阅的异步处理方式,实现数据最终—致性;
微服务内的领域事件
在微服务内发牛领域事件,如果同时更新多个聚合数据时,你需要确保多个聚合数据的一致性。按照DDD一次事务只更新地个聚合的原则。可以引人事件总线,通过事件总线来实现微服务内多聚合数据的最终一致性,或者采用事务机制保证数据强一致性;
微服务之间的领域事件
尽量减少微服务之间的同步服务调用方式,优先采用基干消息中间件的最终一致性设计;
领域事件实现方式
- 事件发布可以在应用服务或领域服务中完成,将领域事件数据发布到事件总线(微服务内)或者消息中间件(微服务之间)。也可以采用定时程序或数据库日志捕获技术,从数据库事件表中获取增量事件数据,发布到消息中间件;
- 领域事件的订阅逻辑一般建议在应用层实现,领域事件的业务处理逻辑一般建议在领域层的领域服务中实现;
6.DDD分层架构
DDD分层架构中包含四层,从上到下依次是: 用户接口层、应用层、领域层和基础层;
用户接口层
- 用户接口层在前后端分离设计时,主要完成后端微服务与前端不匠用户的接口和数据适配;
- 用户接口层主要有facade接口、DTO以及DO数据的组装和转换等代码逻辑;
应用层
- 应用层连接用户接口层和领域层,它是很薄的一层。主要职能是协调领域层多个聚合完成服务的组合利编排。理论上应用层不应该实现领域模型的核心领域逻辑;
- 应用层也是微服务之间服务调用的通道,微服务在应用层可以调用其他微服务的应用服务,完成微服务之间的服务组合和编排;
- 在应用层主要有应用服务、事件订阅和发布等相关代码逻辑。其中,应用服务主要负责服务的组合、编排和转发,处理业务用例的执行顺序以及结果的拼装。在应用服务中还可以进行安全认证、权限校验、事务控制、领域事件发布或订阅等;
领域层
- 领域层位于应用层之下,是领域模型的核心。主要实现领域模型的核心业务逻辑,体现领域模型的业务能力;
- 在设计时,领域层主要关注实现领域对象或者聚合自身的原子业务逻辑,不太关注外部用户操作或者流程等方面的业务逻辑。外部易变的如流程、业务组合和编排的需求由应用层完成。这样设计可以保证领域模型不易受外部需求变化的影响,从而保证领域模型的稳定;
- 领域建模时提取的大部分领域对象都放在领域层。微服务的领域层可能会有多个聚合,聚合内部一般都有聚合根、实体、值对象和领域服务等领域对象。它们组合在一起协同实现领域模型的核心业务能力;
- 领域模型的业务逻辑主要是由实体和领域服务来实现的。其中实体会采用充血模型来实现所有与之相关的业务功能。但实体对象和领域服务在实现业务逻辑上不是同一层级的。当领域中的某些功能,如单一实体(或者值对象)不能实现时,这时领域服务就会出马,组合和协调聚合内的多个实体(或者值对象),实现复杂的业务逻辑;
基础层
基础层贯穿了DDD所有层,它的主要职能就是为其他各层提供通用的技术和基础服务,包括第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。常见的功能是完成实体的数据库持久化;
DDD分层架构的重要原则
- 每层只能与位于其下方的层发生藕合;
- 严格分层架构是指任何层只能对位于其直接下方的层产生依赖;
微服务架构的演进
以聚合作为组合和拆分的基本单元,完成领域模型和微服务架构的演进。可以将聚合作为一个完整单元,在不同的领域模型之间完成重组或者拆分,甚至可以直接将一个聚合独立拆分为微服务;
三层架构如何演进到DDD分层架构
DDD分层架构将三层架构业务逻辑层的业务逻辑拆分到了应用层和领域层分别以应用服务和领域服务等形式存在。应用服务实现服务的组合和编排,领域服务完成核心领域逻辑,应用服务可以快速响应前端业务和流程的变化,而领域层则更加专注领域模型和实现领域逻辑;
7.微服务架构分层
微服务架构模型
- 洋葱架构
- 六边形架构
- DDD分层架构
中台领域模型
领域模型的质量决定了未来微服务的质量,它可以为你带来以下价值:
- 领域模型核心业务逻辑聚焦于核心原子业务逻辑,职责单一,可自由组合出新的复杂服务,受前端页面和流程需求影响会大大降低,有助于提升应用的稳定性;
- 领域模型高度聚焦核心领域逻辑,其代码位于最核心的领域层,有利于精炼核心代码,提高核心代码的复用率,在提升代码质量的前提下,同时降低代码行数量;
- 领域模型业务的高内聚和职责单一的特性,有利于数据内聚和数据质量的提升,有助于数据中台建设;
- 领域模型高内聚,低耦合的聚合边界和解耦策略,有利于提升微服务的架构演进能力;
- 合理的架构分层和职责分工,可有效降低外部需求变化对核心业务逻辑的影响;
微服务架构分层
项目级微服务
- 在项目级微服务内部遵循DDD分层架构模型的规则就可以了。领域模型的核心逻辑在领域层实现,领域服务的组合和编排在应用层实现,用户接口层封装成facadc接口后,发布到APl网关为前端应用提供服务,实现前后端分离;
- 通常项目级微服务之间的集成复杂度相对较小,微服务之间的服务组合利编排,可以在某个关键微服务的应用层通过应用服务组合和编排来完成;
企业级微服务
- 可以在多个微服务上增加一层BFF层(服务于前端的后端),它的主要职能是处理跨中台微服务的服务组合利编排,实现微服务之间的服务和事务的协作。它还可以通过facade接口实现前端不同渠道应用的接口和数据适配。还是一个面向不同行业或渠道应用的服务集成平台;
- BFF微服务与其他微服务存在较大差异,BFF微服务只有应用层和用户接口层的职能,完成各个中台微服务的服务组合利编排,适配不同前端和渠道应用的个性需求,为前端应用提供粗粒度的组合服务。所以它没有领域模型,也不会有领域层它不需要实现领域逻辑;
- BFF微服务与应用服务的差异主要体现在: BFF主要是微服务之间的服务组合和编排,而应用服务主要是微服务内的服务组合和编排;