领域驱动设计(DDD)

概述

DDD 核心思想是通过领域驱动设计方法定义领域模型,从而确定业务和应用边界,保证业务模型与代码模型的一致性。

按照 DDD 方法设计出的微服务的业务和应用边界都非常合理,可以很好地实现微服务内部和外部的“高内聚、低耦合”

为什么 DDD 适合微服务?

DDD 不是架构,而是一种架构设计方法论,它通过边界划分将复杂业务领域简单化,帮我们设计出清晰的领域和应用边界,可以很容易地实现架构演进。

相关名词

  • 领域和子域:

    在研究和解决业务问题时,DDD 会按照一定的规则将业务领域进行细分,当领域细分到一定的程度后,DDD 会将问题范围限定在特定的边界内,在这个边界内建立领域模型,进而用代码实现该领域模型,解决相应的业务问题。

    DDD 的领域就是这个边界内要解决的业务问题域,而子域就是领域再细分,对应一个更小的问题域或更小的业务范围。

    每一个细分的领域都会有一个知识体系,也就是 DDD 的领域模型。

  • 核心域、通用域和支撑域:

    子域可以根据自身重要性和功能属性划分为三类子域,它们分别是:核心域、通用域和支撑域。

    • 核心域:决定产品和公司核心竞争力的子域是核心域,它是业务成功的主要因素和公司的核心竞争力。
    • 通用域:没有太多个性化的诉求,同时被多个子域使用的通用功能子域是通用域。
    • 支撑域:既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域,它就是支撑域。
  • 限界上下文:

    DDD 在战略设计上提出了“限界上下文”这个概念,用来确定语义所在的领域边界。

    限界上下文的定义就是:用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。

  • 实体和值对象:

    这两个概念都是领域模型中的领域对象;实体和值对象是组成领域模型的基础单元。

    • 实体:实体是指那些具有唯一标识的领域对象,这些对象在业务中有明确定义的生命周期和状态。在DDD中,实体是指在整个系统中具有唯一标识并具有业务意义的对象。
    • 值对象:值对象(Value Object)是另一个重要的概念,它与实体(Entity)不同,值对象没有唯一的标识符,而是根据其属性值来比较和区分。值对象通常用于描述实体的属性或组合数据,它们在系统中是不可变的,即一旦创建,就不能被修改。
  • 聚合和聚合根:

    • 聚合:领域模型内的实体和值对象就好比个体,而能让实体和值对象协同工作的组织就是聚合,它用来确保这些领域对象在实现共同的业务逻辑时,能保证数据的一致性。

      聚合就是由业务和逻辑紧密关联的实体和值对象组合而成的,聚合是数据修改和持久化的基本单元,每一个聚合对应一个仓储,实现数据的持久化。

    • 聚合根:聚合根的主要目的是为了避免由于复杂数据模型缺少统一的业务规则控制,而导致聚合、实体之间数据不一致性的问题。

      如果把聚合比作组织,那聚合根就是这个组织的负责人。聚合根也称为根实体,它不仅是实体,还是聚合的管理者。

  • 领域事件:

    事件风暴(Event Storming)时,我们发现除了命令和操作等业务行为以外,还有一种非常重要的事件,这种事件发生后通常会导致进一步的业务操作,在 DDD 中这种事件被称为领域事件。领域事件的引入可以帮助实现解耦和业务分析,支持系统中的事件驱动架构。

    特点:

    1. 领域事件是不可变的:一旦领域事件被创建并发布,其内容就不能再被修改。这样可以确保事件的可靠性和一致性。
    2. 事件的发布和订阅:在DDD中,领域事件通过发布和订阅机制进行传递。当一个领域事件发生时,相关的订阅者可以接收到该事件并做出相应的处理。
    3. 支持业务驱动:领域事件的设计应该紧密关联业务场景,它们描述了在业务领域中发生的重要业务动作和决策。
    4. 与聚合根密切相关:通常,领域事件与聚合根(Aggregate Root)的操作和状态变化相关联。聚合根是DDD中的一个重要概念,用于保持领域对象的一致性和完整性。
    5. 事件溯源:领域事件还可以用于实现事件溯源(Event Sourcing),通过将所有状态变化表示为一系列事件,可以回溯和重建系统的状态。

分层架构

DDD 分层架构就是优化后的四层架构;从上到下依次是:用户接口层、应用层、领域层和基础层。

image-20230729151056570
  1. 用户接口层:

    用户接口层负责向用户显示信息和解释用户指令。这里的用户可能是:用户、程序、自动化测试和批处理脚本等等。

  2. 应用层:

    应用层是很薄的一层,理论上不应该有业务规则或逻辑,主要面向用例和流程相关的操作。但应用层又位于领域层之上,因为领域层包含多个聚合,所以它可以协调多个聚合的服务和领域对象完成服务编排和组合,协作完成业务操作。

    此外,应用层也是微服务之间交互的通道,它可以调用其它微服务的应用服务,完成微服务之间的服务组合和编排。在设计和开发时,不要将本该放在领域层的业务逻辑放到应用层中实现。

    在微服务内部,实体的方法被领域服务组合和封装,领域服务又被应用服务组合和封装。

  3. 领域层:

    领域层的作用是实现企业核心业务逻辑,通过各种校验手段保证业务的正确性。领域层主要体现领域模型的业务能力,它用来表达业务概念、业务状态和业务规则。

    领域层包含聚合根、实体、值对象、领域服务等领域模型中的领域对象。

  4. 基础层:

    基础层是贯穿所有层的,它的作用就是为其它各层提供通用的技术和基础服务,包括第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。比较常见的功能还是提供数据库持久化。

    基础层包含基础服务,它采用依赖倒置设计,封装基础资源服务,实现应用层、领域层与基础层的解耦,降低外部资源变化对应用的影响。

优化后的 DDD 分层架构模型就属于严格分层架构,任何层只能对位于其直接下方的层产生依赖。

传统MVC到DDD四层架构的变化:

image-20230730153752752

代码结构

微服务一级目录是按照 DDD 分层架构的分层职责来定义的。从下面这张图中,我们可以看到,在代码模型里分别为用户接口层、应用层、领域层和基础层,建立了 interfaces、application、domain 和 infrastructure 四个一级代码目录。

image-20230804154123361

Interfaces(用户接口层)

它主要存放用户接口层与前端交互、展现数据相关的代码。前端应用通过这一层的接口,向应用服务获取展现所需的数据。这一层主要用来处理用户发送的 Restful 请求,解析用户输入的配置文件,并将数据传递给 Application 层。数据的组装、数据传输格式以及 Facade 接口等代码都会放在这一层目录里。

image-20230804154222799

  • Assembler:实现 DTO 与领域对象之间的相互转换和数据交换。一般来说 Assembler 与 DTO 总是一同出现。
  • Dto:它是数据传输的载体,内部不存在任何业务逻辑,我们可以通过 DTO 把内部的领域对象与外界隔离。
  • Facade:提供较粗粒度的调用接口,将用户请求委派给一个或多个应用服务进行处理。

Application(应用层)

它主要存放应用层服务组合和编排相关的代码。应用服务向下基于微服务内的领域服务或外部微服务的应用服务完成服务的编排和组合,向上为用户接口层提供各种应用数据展现支持服务。应用服务和事件等代码会放在这一层目录里。

image-20230804154319920

  • Event(事件):这层目录主要存放事件相关的代码。它包括两个子目录:publish 和 subscribe。

    • publish :主要存放事件发布相关代码。
    • subscribe:主要存放事件订阅相关代码。
  • Service(应用服务):

    这层的服务是应用服务。应用服务会对多个领域服务或外部应用服务进行封装、编排和组合,对外提供粗粒度的服务。应用服务主要实现服务组合和编排,是一段独立的业务逻辑。

Domain(领域层)

它主要存放领域层核心业务逻辑相关的代码。领域层可以包含多个聚合代码包,它们共同实现领域模型的核心业务逻辑。聚合以及聚合内的实体、方法、领域服务和事件等代码会放在这一层目录里。

image-20230804154506906

  • Aggregate(聚合):

    它是聚合软件包的根目录,可以根据实际项目的聚合名称命名,比如权限聚合。在聚合内定义聚合根、实体和值对象以及领域服务之间的关系和边界。聚合内实现高内聚的业务逻辑,它的代码可以独立拆分为微服务。

  • Entity(实体):

    它存放聚合根、实体、值对象以及工厂模式(Factory)相关代码。实体类采用充血模型,同一实体相关的业务逻辑都在实体类代码中实现。跨实体的业务逻辑代码在领域服务中实现。

  • Event(事件):它存放事件实体以及与事件活动相关的业务逻辑代码。

  • Service(领域服务):

    它存放领域服务代码。一个领域服务是多个实体组合出来的一段业务逻辑。你可以将聚合内所有领域服务都放在一个领域服务类中,你也可以把每一个领域服务设计为一个类。

  • Repository(仓储):它存放所在聚合的查询或持久化领域对象的代码,通常包括仓储接口和仓储实现方法。

Infrastructure(基础层)

它主要存放基础资源服务相关的代码,为其它各层提供的通用技术能力、三方软件包、数据库服务、配置和基础资源服务的代码都会放在这一层目录里。

image-20230804154933251

  • Config:主要存放配置相关代码。
  • Util:主要存放平台、开发框架、消息、数据库、缓存、文件、总线、网关、第三方类库、通用算法等基础代码,你可以为不同的资源类别建立不同的子目录。