天天看点

《DDD实战课-欧创新》学习笔记领域、子域、核心域、通用域和支撑域限界上下文实体和值对象聚合和聚合根领域事件分层架构代码目录结构

领域、子域、核心域、通用域和支撑域

  1. DDD 的领域就是这个边界内要解决的业务问题域。
  2. 领域可以进一步划分为子领域。我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围。
  3. 领域建模和微服务建设的过程和方法基本类似,其核心思想就是将问题域逐步分解,降低业务理解和系统实现的复杂度。
  4. 在领域不断划分的过程中,领域会细分为不同的子域,子域可以根据自身重要性和功能属性划分为三类子域,它们分别是:核心域、通用域和支撑域。
    1. 决定产品和公司核心竞争力的子域是核心域,它是业务成功的主要因素和公司的核心竞争力。
    2. 没有太多个性化的诉求,同时被多个子域使用的通用功能子域是通用域。
    3. 功能子域是必需的,但既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域,它就是支撑域。
  5. 领域的核心思想就是将问题域逐级细分,来降低业务理解和系统实现的复杂度。通过领域细分,逐步缩小微服务需要解决的问题域,构建合适的领域模型,而领域模型映射成系统就是微服务了。
  6. 核心域、支撑域和通用域的主要目标是:通过领域划分,区分不同子域在公司内的不同功能属性和重要性,从而公司可对不同子域采取不同的资源投入和建设策略,其关注度也会不一样。

限界上下文

  1. 通用语言定义上下文含义,限界上下文则定义领域边界,以确保每个上下文含义在它特定的边界内都具有唯一的含义,领域模型则存在于这个边界之内。
  2. 在事件风暴过程中,通过团队交流达成共识的,能够简单、清晰、准确描述业务涵义和规则的语言就是通用语言。
  3. DDD 分析和设计过程中的每一个环节都需要保证限界上下文内术语的统一,在代码模型设计的时侯就要建立领域对象和代码对象的一一映射,从而保证业务模型和代码模型的一致,实现业务语言与代码语言的统一。
  4. 用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。这个边界定义了模型的适用范围,使团队所有成员能够明确地知道什么应该在模型中实现,什么不应该在模型中实现。
  5. 理论上限界上下文就是微服务的边界。我们将限界上下文内的领域模型映射到微服务,就完成了从问题域到软件的解决方案。

实体和值对象

  1. 在 DDD 中有这样一类对象,它们拥有唯一标识符,且标识符在历经各种状态变更后仍能保持一致。对这些对象而言,重要的不是其属性,而是其延续性和标识,对象的延续性和标识会跨越甚至超出软件的生命周期。我们把这样的对象称为实体。
    1. 实体的业务形态:领域模型中的实体是多个属性、操作或行为的载体。实体和值对象是组成领域模型的基础单元。
    2. 实体的代码形态:在代码模型中,实体的表现形式是实体类,这个类包含了实体的属性和方法,通过这些方法实现实体自身的业务逻辑。在 DDD 里,这些实体类通常采用充血模型,与这个实体相关的所有业务逻辑都在实体类的方法中实现,跨多个实体的领域逻辑则在领域服务中实现。
    3. 实体的运行形态:实体以 DO(领域对象)的形式存在,每个实体对象都有唯一的 ID。可以对一个实体对象进行多次修改,修改后的数据和原来的数据可能会大不相同。但是,由于它们拥有相同的 ID,它们依然是同一个实体。
    4. 实体的数据库形态:,DDD 是先构建领域模型,针对实际业务场景构建实体对象和行为,再将实体对象映射到数据持久化对象。一个实体可能对应 0 个、1 个或者多个数据库持久化对象。
  2. 在 DDD 中用来描述领域的特定方面,并且是一个没有标识符的对象,叫作值对象。值对象描述了领域中的一件东西,这个东西是不可变的,它将不同的相关属性组合成了一个概念整体。
    1. 值对象的业务形态:实体是看得到、摸得着的实实在在的业务对象,实体具有业务属性、业务行为和业务逻辑。而值对象只是若干个属性的集合,只有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑。
    2. 值对象的代码形态:如果值对象是单一属性,则直接定义为实体类的属性;如果值对象是属性集合,则把它设计为 Class 类,Class 将具有整体概念的多个属性归集到属性集合,这样的值对象没有 ID,会被实体整体引用。
    3. 值对象的运行形态:值对象嵌入到实体的话,有这样两种不同的数据格式,也可以说是两种方式,分别是属性嵌入的方式和序列化大对象的方式。
    4. 值对象的数据库形态:在领域建模时,我们可以将部分对象设计为值对象,保留对象的业务涵义,同时又减少了实体的数量;在数据建模时,我们可以将值对象嵌入实体,减少实体表的数量,简化数据库设计。
  3. 值对象采用序列化大对象的方法简化了数据库设计,减少了实体表的数量,可以简单、清晰地表达业务概念。这种设计方式虽然降低了数据库设计的复杂度,但却无法满足基于值对象的快速查询,会导致搜索值对象属性值变得异常困难。
  4. 值对象采用属性嵌入的方法提升了数据库的性能,但如果实体引用的值对象过多,则会导致实体堆积一堆缺乏概念完整性的属性,这样值对象就会失去业务涵义,操作起来也不方便。
  5. 值对象和实体在某些场景下可以互换。值对象在某些场景下有很好的价值,但是并不是所有的场景都适合值对象。同样的对象在不同的场景下,可能会设计出不同的结果。

聚合和聚合根

  1. 聚合的设计原则
    1. 在一致性边界内建模真正的不变条件。聚合用来封装真正的不变性,而不是简单地将对象组合在一起。聚合内有一套不变的业务规则,各实体和值对象按照统一的业务规则运行,实现对象数据的一致性,边界之外的任何东西都与该聚合无关,这就是聚合能实现业务高内聚的原因。
    2. 设计小聚合。如果聚合设计得过大,聚合会因为包含过多的实体,导致实体之间的管理过于复杂,高频操作时会出现并发冲突或者数据库锁,最终导致系统可用性变差。而小聚合设计则可以降低由于业务过大导致聚合重构的可能性,让领域模型更能适应业务的变化。
    3. 通过唯一标识引用其它聚合。聚合之间是通过关联外部聚合根 ID 的方式引用,而不是直接对象引用的方式。外部聚合的对象放在聚合边界内管理,容易导致聚合的边界不清晰,也会增加聚合之间的耦合度。
    4. 在边界之外使用最终一致性。聚合内数据强一致性,而聚合之间数据最终一致性。
    5. 通过应用层实现跨聚合的服务调用。
  2. 聚合的特点:高内聚、低耦合,它是领域模型中最底层的边界,可以作为拆分微服务的最小单位,但我不建议你对微服务过度拆分。但在对性能有极致要求的场景中,聚合可以独立作为一个微服务,以满足版本的高频发布和极致的弹性伸缩能力。
  3. 聚合根的特点:聚合根是实体,有实体的特点,具有全局唯一标识,有独立的生命周期。一个聚合只有一个聚合根,聚合根在聚合内对实体和值对象采用直接对象引用的方式进行组织和协调,聚合根与聚合根之间通过 ID 关联的方式实现聚合之间的协同。
  4. 实体的特点:有 ID 标识,通过 ID 判断相等性,ID 在聚合内唯一即可。状态可变,它依附于聚合根,其生命周期由聚合根管理。实体一般会持久化,但与数据库持久化对象不一定是一对一的关系。实体可以引用聚合内的聚合根、实体和值对象。
  5. 值对象的特点:无 ID,不可变,无生命周期,用完即扔。值对象之间通过属性值判断相等性。它的核心本质是值,是一组概念完整的属性组成的集合,用于描述实体的状态和特征。值对象尽量只引用值对象。

领域事件

  1. 领域事件驱动设计可以切断领域模型之间的强依赖关系,事件发布完成后,发布方不必关心后续订阅方事件处理是否成功,这样可以实现领域模型的解耦,维护领域模型的独立性和数据的一致性。在领域模型映射到微服务系统架构时,领域事件可以解耦微服务,微服务之间的数据不必要求强一致性,而是基于事件的最终一致性。
  2. 当领域事件发生在微服务内的聚合之间,领域事件发生后完成事件实体构建和事件数据持久化,发布方聚合将事件发布到事件总线,订阅方接收事件数据完成后续业务操作。不一定需要引入消息中间件。
  3. 跨微服务的领域事件会在不同的限界上下文或领域模型之间实现业务协作,其主要目的是实现微服务解耦,减轻微服务之间实时服务访问的压力。
  4. 领域事件处理包括:事件构建和发布、事件数据持久化、事件总线、消息中间件、事件接收和处理等。
    1. 事件基本属性至少包括:事件唯一标识、发生时间、事件类型和事件源,其中事件唯一标识应该是全局唯一的,以便事件能够无歧义地在多个限界上下文中传递。事件基本属性主要记录事件自身以及事件发生背景的数据。

      另外事件中还有一项更重要,那就是业务属性,用于记录事件发生那一刻的业务数据,这些数据会随事件传输到订阅方,以开展下一步的业务操作。

    2. 事件数据持久化可用于系统之间的数据对账,或者实现发布方和订阅方事件数据的审计。当遇到消息中间件、订阅方系统宕机或者网络中断,在问题解决后仍可继续后续业务流转,保证数据的一致性。

      事件数据持久化有两种方案,

      • 持久化到本地业务数据库的事件表中,利用本地事务保证业务和事件数据的一致性。
      • 持久化到共享的事件数据库中。这里需要注意的是:业务数据库和事件数据库不在一个数据库中,它们的数据持久化操作会跨数据库,因此需要分布式事务机制来保证业务和事件数据的强一致性,结果就是会对系统性能造成一定的影响。
    3. 事件总线是实现微服务内聚合之间领域事件的重要组件,它提供事件分发和接收等服务。事件总线是进程内模型,它会在微服务内聚合之间遍历订阅者列表,采取同步或异步的模式传递数据。事件分发流程大致如下:
      • 如果是微服务内的订阅者(其它聚合),则直接分发到指定订阅者;
      • 如果是微服务外的订阅者,将事件数据保存到事件库(表)并异步发送到消息中间件;
      • 如果同时存在微服务内和外订阅者,则先分发到内部订阅者,将事件消息保存到事件库(表),再异步发送到消息中间件。
    4. 跨微服务的领域事件大多会用到消息中间件,实现跨微服务的事件发布和订阅。
    5. 微服务订阅方在应用层采用监听机制,接收消息队列中的事件数据,完成事件数据的持久化后,就可以开始进一步的业务处理。领域事件处理可在领域服务中实现。

分层架构

《DDD实战课-欧创新》学习笔记领域、子域、核心域、通用域和支撑域限界上下文实体和值对象聚合和聚合根领域事件分层架构代码目录结构

三层架构向DDD分层架构演进,主要发生在业务逻辑层和数据访问层。

DDD分层架构在用户接口层引入了DTO,给前端提供了更多的可使用数据和更高的展示灵活性。

DDD分层架构对三层架构的业务逻辑层进行了更清晰的划分,改善了三层架构核心业务逻辑混乱,代码改动相互影响大的情况。DDD分层架构将业务逻辑层的服务拆分到了应用层和领域层。应用层快速响应前端的变化,领域层实现领域模型的能力。

另外一个重要的变化发生在数据访问层和基础层之间。三层架构数据访问采用DAO方式;DDD分层架构的数据库等基础资源访问,采用了仓储(Repository)设计模式,通过依赖倒置实现各层对基础资源的解耦。

仓储又分为两部分:仓储接口和仓储实现。仓储接口放在领域层中,仓储实现放在基础层。原来三层架构通用的第三方工具包、驱动、Common、Utility、Config等通用的公共的资源类统一放到了基础层。

代码目录结构

《DDD实战课-欧创新》学习笔记领域、子域、核心域、通用域和支撑域限界上下文实体和值对象聚合和聚合根领域事件分层架构代码目录结构
  1. interfaces:用户接口层,主要存放用户接口层与前端交互、展现数据相关的代码。用来处理用户发送的Restful请求,解析用户输入的配置文件,并将数据传递给Application层。数据的组装、数据传输格式以及Facade接口等代码都会放在这一层目录里。
    1. assembler:实现DTO与领域对象之间的相互转换和数据交换。(DTO接收前端传递的参数)
    2. dto:数据传输的载体,内部不存在任何业务逻辑,可以通过DTO把内部的领域对象与外界隔离。
    3. Facade:提供较粗粒度的调用接口,将用户请求委派给一个或多个应用服务进行处理。
  2. Application:应用层,主要存在应用层服务组合和编排相关的代码。基于微服务内的领域服务或外部微服务的应用服务完成服务的编排和组合,为用户接口层提供各种数据展现支持服务。应用服务和事件代码会放在这一层目录里。
    1. event:事件,主要存放事件相关代码,包括两个子目录:publish和subscribe。前者主要存放事件发布相关代码,后者主要存放事件订阅相关代码(事件处理相关的核心业务逻辑在领域层实现)。
    2. service:应用服务,对多个领域服务或外部应用服务进行封装、编排和组合,对外提供粗粒度的服务。
  3. Domain:领域层,主要存放领域层核心业务逻辑相关的代码。可以包含多个聚合代码包,它们共同实现领域模型的核心业务逻辑。聚合以及聚合内的实体、方法、领域服务和事件等代码会放在这一层目录里。
    1. Aggregate:聚合,聚合软件包的根目录,可以根据实际项目的聚合名称明明。在聚合内定义聚合根、实体和值对象以及领域服务之间的关系和边界。聚合内实现高内聚的业务逻辑,代码可以独立拆分为微服务。
    2. Entity:实体,存放聚合根、实体、值对象以及工厂模式相关代码。实体类采用充血模型,统一实体相关的业务逻辑都在实体类代码中实现。跨实体的业务逻辑代码在领域服务中实现。
    3. Event:事件,存放事件实体以及事件活动相关的业务逻辑代码。
    4. Service:领域服务,存放领域服务代码。一个领域服务是多个实体组合出来的一段业务逻辑。可以将聚合内所有领域服务都放在一个领域服务类中,也可以把每一个领域服务设计为一个类。
    5. Repository:仓储,存放所在聚合的查询或持久化领域对象的代码,通常包括仓储接口和仓储实现方法。一个聚合对应一个仓储。
  4. infrastructure:基础层,主要存放基础资源服务相关的代码,为其他各层提供的通用技术能力、三方软件包、数据库服务、配置和基础资源服务的代码都会放在这一层目录里。
    1. Config:主要存放配置相关代码。
    2. Util:主要存放平台、开发框架、消息、数据库、缓存、文件、总线、网关、第三方类库、通用算法等基础代码。可以为不同的资源类别建立不同的子目录。