一、DDD领域驱动模型

image-20240218150002015

一 、认识领域模型

1
DDD是一种软件设计思想和方法论,以领域为核心构建软件设计体系,将业务模型抽象成领域模型进行拆解和封装。 Domain Driven Design(领域驱动设计, DDD),不是一种架构,而是一种架构方法论,是一种拆解业务、划分业务、确定业务边界的方法,是一种领域设计思想。 DDD(领域驱动设计)实际上是一套软件架构设计的方法论,我们可以在此之上更好的理解业务。并且我们可以根据这套方法论进行架构风格填充,包括微服务架构,面向服务架构,REST风格架构以及六边形架构等等。

解决的问题:软件开发完成后,因需求的变更导致软件不得不增加新的功能,软件越来越冗余,导致最后不得不重构,领域驱动设计能分析好领域相关的业务逻辑,确定好边界,确定同一沟通预言,让软件通过领域模型设计后,更易于扩展。

核心思想:领域驱动的核心思想就是将问题慢慢细化,同时找出最核心的问题点,倾斜资源保证核心资源不出问题,同时也可以通过领域的细分,去慢慢的缩小我们的子域所要解决的问题,并且构建合适的领域模型。

个人理解:我们用软件去解决一些问题,首先要确定软件的边界,确定软件解决的问题,然后围绕这个根本问题(领域),去划分成多个子问题(子领域),分治的方法解决复杂软件的设计问题,划分之后通过一些方法,对子领域进行分析,画出领域模型,基于这种思想,为了达到这种目的,领域驱动设计会有很多其他具体步骤和划分实体等分类,并且有集中领域模型应对不同的情况,牵扯到模式,还有如何开会去讨论领域模型,总之为了解决复杂领域的软件设计,领域驱动不只是空的方法论,里面有很多小的方法来支撑他的各种操作。

一、DDD基本概念理解

1.1 什么是DDD?

领域驱动设计DDD(Domain Driven Design)是一种从系统分析到软件建模的设计思想和方法论,最早在2004年由Eric Evans在著作《Domain-Driven Design –Tackling Complexity in the Heart of Software》中提出相应的概念。其核心思想是以领域为核心驱动力构建软件设计体系,并围绕业务概念抽象出领域模型,通过领域和边界划分将复杂的业务模型抽象化、简单化,最终实现复杂软件应用系统的拆解和封装。DDD不仅可以用于微服务设计,还可以很好地应用于企业中台的设计,也适用于传统的单体应用。

1
在DDD中分为战略设计和战术设计: 1.战略设计:主要从业务视角出发,建立业务领域模型、划分领域边界、建立通用语言的限界上下文。在战略设计中,需要对领域进行全面的了解和分析,探究业务的规则和本质,以及领域未来的趋势发展和可能变化。 2.战术设计:从技术视角出发,侧重于领域模型的技术实现完成软件开发和落地,包括:实体、值对象、聚合、工厂、仓库、领域服务、领域事件等代码逻辑的设计和实现。战术设计关注的是领域中的具体情境和场景,需要针对具体的问题进行具体的分析和设计,以满足业务需求。
1.2 DDD和微服务关系

这些年随着软硬件技术的发展,软件设计架构也从最早的单机架构(BS/CS)、到集中式架构,再到如今微服务(MicroServices)分布式架构。

1
1.单机架构:采用面向过程的设计方法,系统包括应用客户端层和数据库层,整个系统围绕数据库驱动设计和开发。 2.集中式架构:采用面向对象的设计方法,经典的三层架构包括业务接入层、业务逻辑层和数据访问层。这种集中式架构容易在某一层变得臃肿,另外受限于集中式的限制,可扩展性差,架构上不灵活。 3.分布式微服务架构:微服务架构将应用程序拆分为一系列相互协作的微服务,每个微服务负责一个特定的业务领域或业务功能,从而实现应用之间的解耦,同时解决单体应用扩展性和灵活性问题。在微服务架构中,每个微服务都是独立的,可以使用不同的编程语言、数据存储和框架,这使得每个微服务可以独立地演进和部署。

image-20240218173553791

DDD的概念最早在2004年就提出来,之后一直不温不火直到微服务的概念出现,也可以说DDD的流行和微服务的发展有一定的关系。微服务的兴起使得越来越多的开发人员开始关注如何将应用程序划分成一系列相互协作的微服务,并且每个微服务需要实现自己的业务逻辑。在这个过程中,开发人员发现他们需要一种更好的方式来划分业务领域边界,建立正确的业务模型和通用语言,以便更好地实现微服务的独立性和可维护性。

DDD正是一种能够帮助开发人员更好地设计和实现业务领域的模式。通过将业务领域作为核心,DDD可以帮助开发人员更好地理解业务需求,建立正确的业务模型和通用语言,从而更好地划分微服务边界,从而更好地实现微服务的独立性和可维护性。

1.3 DDD中基本概念
1
领域驱动设计DDD中的核心概念如下图所示,包括: 1.战略设计:包括领域/子域、统一语言、限界上下文等概念 2.战术设计:使用这些模式来捕获和传递领域中的概念、关系、规则 --代表领域的概念:如实体、值对象、领域服务、领域事件等 --用于管理对象的生命周期:如聚合、工厂、资源库等 --用于集成或跟踪:领域事件、事件溯源等

image-20240219100809260

1.3.1 统一语言

DDD中的统一语言(Ubiquitous Language)是一种用于描述业务领域中的概念、规则和流程的语言。它是一种由领域专家、开发人员和用户共同理解的通用语言,而不是一种特定的编程语言或框架。

统一语言贯穿于整个开发过程,从需求分析到设计再到编码。它应该用于描述业务领域中的实体、值对象、聚合、领域服务等概念,同时也要用于描述业务规则、流程和交互。
在统一语言中,应该使用业务领域的术语和词汇,而不是技术术语和词汇。例如,应该使用“客户”而不是“用户”来描述应用程序的客户端,因为“客户”是业务领域中的术语,而“用户”则是技术领域的术语。

统一语言的重要性在于,它能够帮助开发人员更好地理解业务需求和领域模型,同时也能帮助业务专家更好地理解技术和系统实现。它还能够提高代码的可读性和可维护性,因为代码中的术语和词汇能够被业务专家和开发人员共同理解。

1.3.2 领域和子域

在研究和解决业务问题时,DDD会按照一定的业务规则将业务领域进行细分,当领域细分到一定的程度后,DDD会将问题范围限定在特定的边界内,在这个边界内建立领域模型。简言之,领域是业务领域的范围和边界。

领域可以进一步划分为不同的子领域称为子域,每个子域都包含了一些特定的业务规则和功能,它们共同构成了整个业务领域。领域和子域是基于业务需求和功能进行划分,在划分领域和子域时,需要考虑到业务需求的变化和扩展,使得系统能够更好地适应业务需求的变化。

领域和子域的划分也是一种设计上的分层,它能够帮助开发人员更好地理解和组织系统中的代码和模型。在实现时,每个子域都可以被划分成一个或多个限界上下文(Boundary Context),每个限界上下文都包含了一些特定的业务规则和功能,它们共同实现了该子域的功能。

1.3.3 核心域、通用域和支撑域

领域在不断划分的过程中,细分为不同的子域,根据重要性的不同,子域又划分为核心域、通用域和支撑域。

  1. 核心域是指业务领域中的核心业务逻辑和模型,它们是领域驱动设计的核心部分。核心域包含领域模型、领域服务、业务规则等,它们是业务领域中最重要、最稳定的部分。
  2. 通用域是指业务领域中通用的、可复用的业务逻辑和模型,它们不是核心业务逻辑,但可能在多个核心域中被使用。通用域包含一些通用的业务逻辑、数据校验、安全控制等。
  3. 支撑域是指用于支持业务领域中的核心域和通用域的基础设施和工具,包括数据存储、消息传递、分布式系统、自动化测试等。支撑域中不包含业务逻辑和模型,而是聚焦于技术实现和系统监控、管理等方面。
1.3.4 界限上下文

限界上下文是指一个业务领域的边界,它包含了多个子域(Subdomain),每个子域都包含了一些特定的业务规则和功能。限界上下文用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。

限界上下文是一个显式的边界,领域模型便存在于这个边界之内;在边界内,通用语言中的所有术语和词组都有特定的含义,而模型需要准确地反映通用语言。通过限界上下文,开发人员可以更好的组织和管理领域中的不同部分,更好的理解业务需求和领域模型。

领域、子域和限界上下文的关系如下图所示:
image-20240219103635471

1.3.5 实体Entity

实体是拥有唯一标识和状态,且具有生命周期的业务对象。在实体的生命周期内,无论其如何变化,其仍旧是同一个实体。实体的标识和状态可以在业务过程中发生变化,而实体之间的关系也可以随着业务过程的变化而发生变化。

  • 实体的业务形态:在领域模型中实体是多个属性、操作或行为的载体。实体的属性描述实体的状态比如客户的姓名、年龄等;实体的行为指实体可以执行的操作,比如创建、更新等。实体和值对象是组成领域模型的基础单元。
  • 实体的代码形态:在代码模型中,实体表现为实体类,这个类包含了实体的属性和方法,通过这些方法实现实体自身的业务逻辑。
  • 实体的运行形态:实体是以DO(领域对象)的形式存在,每个实体对象都有一个唯一的ID,这个ID在实体的整个生命周期内无论实体对象怎么变化都具有唯一性。
  • 实体的数据库形态:DDD先构建领域模型,再针对业务场景构建实体对象和行为,最后将实体对象映射到数据持久化对象。在领域模型映射到数据模型时,一个实体可能对应0个、1个或者多个数据库持久化对象。
1.3.6 值对象
1
《实现领域驱动设计》一书中对值对象的定义如下: 通过对象属性值来识别的对象,它将多个相关属性组合为一个概念整体。在DDD中用来描述领域的特定方面,并且是一个没有标识符的对象,叫作值对象。值对象没有唯一标识,没有生命周期,不可修改,当值对象发生改变时只能替换。

值对象将不同的相关属性组成一个整体概念,是一堆不可变属性的集合。

  • 值对象的业务形态:值对象是若干不可修改的属性集合,只有数据初始化操作和有限不涉及数据修改的行为,基本不包含业务逻辑。值对象在逻辑上依然是实体属性的一部分
  • 值对象的代码形态:如果值对象是单一属性例如字符串、整型、枚举等,则直接定义为实体类的属性;如果值对象是属性集合,则把它设计为Class类,包含多个属性。
  • 值对象的运行形态:值对象嵌入到实体中,有两种不同的数据格式分别是属性嵌入的方式和序列化大对象的方式,比如JSON字段
1.3.7 聚合和聚合根

1)聚合和聚合根

DDD中实体和值对象是很基础的领域对象,实体一般对应业务对象、值对象一般是属性集合,但是实体和值对象都是个体化的表现。而聚合是把一些关联性极强、生命周期一致的实体、值对象放到一个聚合里。聚合根(Aggregate Root)是指聚合中的实体,它负责聚合的内聚性和一致性,同时也是聚合的入口点。

聚合有一个聚合根和上下文边界,这个边界根据业务单一职责和高内聚原则,定义了聚合内部应该包含哪些实体和值对象,而聚合之间的边界是松耦合的。按照这种方式设计出来的微服务很自然就是“高内聚、低耦合”的。

image-20240219103549873

2)聚合设计原则

在领域驱动设计中,聚合设计是非常重要的一部分。以下是聚合设计的一些原则

  • 保持完整性:聚合中包含一组相互关联的实体和值对象,它们被视为一个整体。聚合的边界应该明确,以保持其完整性。
  • 保持一致性:聚合内数据强一致性,而聚合之间数据最终一致性。聚合中的实体和值对象在业务规则和约束下保持一致。如果一个聚合违反了业务规则或约束,那么它就不是一个一致的聚合。
  • 保持小型聚合:聚合尽可能小,只包含必要的实体和值对象。小型聚合可以降低由于业务过大导致聚合重构,有助于提高系统的可维护性和可扩展性。
  • 避免聚合根以外的实体:聚合根以外的实体应该尽可能避免,因为它们增加了系统的复杂性和耦合度。如果必须使用这些实体,应该确保它们与聚合根之间的关系是一致的。
  • 避免跨聚合引用:如果一个实体或值对象需要引用另一个聚合中的实体或值对象,通过关联外部聚合根ID的方式引用,而不是直接对象引用的方式。这样可以保持聚合的独立性和可维护性。
1.3.8 工厂

如果实体或值对象的创建过程非常复杂,可以将其委托给工厂。DDD中工厂是指用于创建领域模型对象的工厂方法,它用于将业务逻辑和实现技术解耦。它是一种设计模式,允许开发人员通过调用工厂方法来创建对象,而不需要直接在代码中实例化对象。

1
将创建复杂对象和聚合的职责分配给一个单独的对象,该对象本身并不承担领域模型中的职责,但是依然是领域设计的一部分。工厂应该提供一个创建对象的接口,该接口封装了所有创建对象的复杂操作过程,同时,它并不需要客户去引用那个实际被创建的对象。对于聚合来说,我们应该一次性地创建整个聚合,并且确保它的不变条件得到满足。
1.3.9 仓库

仓库(Repository)是一种模式,用于封装数据访问逻辑,提供对数据的持久化和查询。仓库操作的最小单元就是聚合,每个聚合会对应一个仓库。它旨在将数据访问细节与领域模型分离,使领域模型更加独立和可测试。

image-20240219103327236

仓库提供了一种统一的接口,使得领域模型可以与不同的数据存储方式进行交互,同时也提供了一些查询操作。例如,一个客户仓库可以具有存储客户、检索客户等方法。通过调用仓库方法,开发人员可以存储和检索客户对象,同时也可以在存储和检索对象时执行其他的业务逻辑和验证。

1.3.10 领域服务

领域服务(Domain Service)是指跨越多个聚合或子域的逻辑服务,用于处理业务需求和行为。领域服务通常用于执行跨聚合的操作,例如处理业务规则、集成外部系统、执行复杂业务流程等。

需要注意的是,实体和领域服务在实现业务逻辑上不是同级的,当领域中的某些功能,单一实体或者值对象不能实现时,领域服务就会出马,它可以组合聚合内的多个实体或者值对象,实现复杂的业务逻辑。

1.3.11 领域事件

领域事件(Domain Event)是指业务领域中发生的一些事件,通常意味着领域对象状态的改变。领域事件在系统中起到了传递消息、触发其他动作的作用,是解耦领域模型的重要手段之一。在实现时,领域事件可以由领域模型中的实体或服务触发,例如当用户注册成功时,可以触发一个“用户注册成功”的领域事件。领域事件也可以由外部系统或服务触发,例如当第三方支付系统支付成功时,可以触发一个“订单支付成功”的领域事件。

领域事件处理包括:事件构建和发布、事件数据持久化、事件总线、消息中间件、事件接收和处理等。
image-20240219093044046

  • 事件构建和发布:
    1.事件基本属性通常包含事件的标识符、时间戳和事件数据等信息;业务属性用于记录事件发生时刻的业务数据。事件的基本属性和业务属性一起构成事件实体。
    2.事件发布方式很多,可以通过应用服务或领域服务发布到事件总线或消息中间件;也可以从事件列表中定时获取增量事件数据发布到消息中间件。
  • 事件数据持久化:用于系统之间的数据对账,或者实现发布方和订阅方数据的审计
    1.持久化到本地业务数据库的事件表中,利用本地事务保证业务和事件数据的一致性
    2.持久化到共享的事件数据库中,事件数据库和业务库是独立的。
  • 事件总线:实现微服务内聚合之间领域事务,提供事务分发和接收等服务
  • 消息中间件:跨微服务的领域事件大多会用到消息中间件,实现跨微服务的事件发布和订阅
  • 事件接收和处理:订阅方采用监听机制接收消息队列中的事件数据,当完成事件数据的持久化处理后,就可以进行下一步的业务处理
1.3.12 贫血模型和充血模型

1)贫血模型(Anemic Domain Model)

所谓的贫血模型指的是定义领域对象的时候,只有对象的属性信息,没有对象的行为信息。贫血模型强调了业务逻辑和数据之间的分离,将业务逻辑和数据分别放在不同的对象中,导致领域模型变得贫血、不灵活和难以维护。

2)充血模型(Rich Domain Model)

充血模型指的是领域对象既包括属性信息,又包含行为信息。在充血模型中,领域对象包含了业务逻辑、数据和规则,它们被组织在一个聚合根中。
相对而言贫血模型相对简单,模型上只有数据没有行为,针对简单的模型可以快速的完成交付,但是后期的成本较高,并且难以保证事务的一致性和完整性。充血模型则是领域模型模式,实现逻辑上由各自的对象负责,具备事务的一致性和完整性,事务边界也被包含在聚合根中。

1.4 DDD领域驱动设计中架构模型

在DDD领域中,常见的领域驱动设计架构有以下几种:

  1. 分层架构:将领域模型和业务逻辑分离出来,并减少对基础设施和应用层的依赖,从最早三层架构,演化到四层架构、五层架构,包括用户接口层、应用层、领域层和基础设施层。分层架构中,每层只能与位于其下方的层耦合。
  2. 六边形架构:也称为端口与适配器。对于每种外界类型,都有一个适配器与之相对应。该架构的定义涉及六个部分,分别是应用程序、域、基础设施、适配器、API和外部系统。六边形架构的核心理念是应用通过端口与外部进行交互。
  3. CQRS架构:CQRS架构是一种读写分离的架构设计,系统被分为两个部分:命令端(Command)和查询端(Query)。命令端负责处理写操作(例如修改数据),而查询端则负责处理读操作(例如查询数据)。
  4. 事件驱动架构:用于处理事件的生成、发现和处理等任务的软件架构。在这种架构中,领域对象之间通过发布和订阅事件来通信,一个事件通常会引发多个服务的同步,从而引起事件的扩散。
1.5 DDD领域驱动设计的优缺点

实现DDD的最大好处是第一时间将业务需求构建成领域模型,而不是将其切割为数据和行为,然后再用数据库去实现。DDD具有以下优点:

  • 统一业务语言:通过使用统一的领域语言,准确传达业务规则,提升团队间的沟通效率。
  • 清晰业务边界:统一各个子域的认识,通过领域模型界定需求实现边界。
  • 提升变化应对:通过领域模型与数据模型的分离,将核心业务的不变与需求的变进行有效隔离,提升架构应变化的能力。

当然使用DDD需要一定的学习和使用成本,开发人员需要掌握一定的领域建模知识和技能。同时在高并发场景下,由于DDD使用的模型可能会增加一些额外的开销。

二、DDD主要架构模型

2.1 分层架构
1
DDD的分层架构在不断的优化,在最早的四层架构中(如下图所示),基础设施层被其它层依赖,处于最核心的位置,但实际上领域层才是依赖的核心。后来采用依赖倒置的设计,优化了传统的四层架构,实现了各层对基础设施层的解耦。四层架构包括:用户接口层、应用层、领域层和基础设施层。

image-20240219104512242

  1. 用户接口层:负责展示用户界面和处理用户输入,它通常包含一些前端框架和UI组件
  2. 应用层:负责处理业务逻辑和应用程序的流程,它通常包含一些应用程序服务,例如事务处理、规则验证和流程管理等
  3. 领域层:领域层是领域模型的核心,它负责表示业务领域中的实体、值对象、聚合、领域服务等。领域层通常包含一些领域模型类、领域服务类和领域事件等。
  4. 基础设施层:负责提供一些通用服务和功能,例如数据存储、消息传递、日志记录等。基础设施层通常包含一些工具类、库和框架等。

在传统架构中,由于上层应用对数据库的强耦合,很多业务在架构演进中担心更换数据库对应用的影响,因为一旦更换数据库,就可能需要重写大部分代码,这对开发来说是致命的。采用依赖倒置的设计后,应用层通过解耦保持独立的核心业务逻辑,当数据库变更后,只需要更换数据库基础服务即可,对应用的影响降到最低。

2.2 六边形架构
1
六边形架构又称为“端口-适配器”架构,对于每种外界类型,都有一个适配器与之相对应,其核心设计思想是外界通过应用API与内部进行交互。 如图所示的六边形架构中,核心业务逻辑(应用程序和领域模型)与外部资源(包括Web应用、基础设施等)完全隔离,仅通过适配器进行交互。除了核心的领域模型,还包括如下三个层次:

image-20240219105348266

  • 应用程序层(Application Layer):负责处理业务逻辑和应用程序的流程,它通常包含一些应用程序服务,例如事务处理、规则验证和流程管理等。
  • 适配器层(Adapter Layer):负责与外部系统进行交互,例如与数据库、消息队列、Web 服务等进行通信和交互。它通常包含一些适配器类和工具类等。
  • 基础设施层(Infrastructure Layer):负责提供一些通用服务和功能,例如数据存储、消息传递、日志记录等。基础设施层通常包含一些工具类、库和框架等。

在六边形架构中,一个端口可能对应多个外部系统,不同的外部系统也可能会使用不同的适配器,由适配器负责协议转换。因此在使用时,根据用例来设计应用程序,所有的适配器使用相同的API满足不同的端口访问需求。

2.3 CQRS架构

CQRS是一种读写分离的架构设计,系统分为两个部分:命令端和查询端,命令端负责处理写操作,而查询端负责处理读操作。在CQRS架构中,写操作和读操作被分离并分布在两个独立的系统中。命令端和查询端之间的数据一致性采用最终一致性的方式处理。这意味着查询端可能会返回不是最新的数据,但系统会确保在一定时间内数据达到一致状态。
image-20240219105937108

除了分离写操作和读操作,CQRS架构还涉及到事件的概念。事件表示命令操作领域中的聚合根状态发生变化后产生的事件。这些事件可以被其他服务订阅,以实现系统的联动。

二、对比MVC结构DDD优势

一、开发成本

1
使用 DDD 模式开发代码的成本到底在哪?是因为使用 DDD 四层分层结构就比MVC 三层分层结构 更浪费时间吗? 其实并不是,因为四层结构相对于三层结构,反而更好的区分了代码所属职责,在熟悉模块功能职责后,开发起来也会更加顺畅。 那这里的 DDD 领域驱动设计开发的成本在哪呢?这个成本在于对于一个复杂系统又尚未在开发前期就有非常充足的经验来拆分职责边界、划分功能领域、明确编排逻辑和对未知流程扩展的把控上,所带来的风暴模型设计成本。 而通常使用的 MVC 结构基本不会出现这样的问题,因为在实际的代码中,DAO、PO、VO等都是共用的,大家在开发代码的时候,像堆泥球一样面向过程写代码,直接串联出产品的PRD功能节点即可,不用过多的思考解耦和内聚。那不是可以设计模式吗,这就需要看你是站在哪个维度去思考问题。设计模式在这里是战术问题的,DDD和MVC是确定战略问题的,有点像是说:“方向不对,努力白费一样”
  • 与其他两种分层结构相对比,使用 DDD 的时候,需要在前期投入较多的时间成本来设计领域建模,所以前期成本会更高一些。
  • 但随着业务不断迭代后的逻辑的复杂性增加,DDD 系统架构所开发的代码稳定性会更好,也就说明 DDD 更容易扩容和维护。
  • 所以框架结构的更换,不是最终增加开发成本的地方,如果你不做领域建模也不做更多的设计思考,那么即使是 DDD 的四层架构,也能让你写出 MVC 的效果。而那些对业务场景经验丰富的架构师或者研发人员,已经非常明确了各个业务功能的职责边界,要实现一个系统需求需要完成哪些核心领域服务,再这样的情况用 DDD 也不会带来多少开发成本,反而更加游刃有余了!这就是为什么说,需要领域专家,因为专家已经积累了很多的战略设计经验
  • 此外使用 DDD 领域驱动设计的模式进行开发,除了解决需求的迭代成本,更多的时候是要面对公司战略调整后,系统的交接、人员的更替和新增,都要在原有的工程架构下继续迭代开发,否则就要推翻重新做,那样所面临的更替成本将更大,同时又是开发了一个与人员绑定不易于交接维护的工程代码。

二、架构对比

1、MVC

MVC 分层结构将:“状态”(数据,成员对象)、“行为“(逻辑、过程),分离到不同的对象中,只有状态的对象(VO -> Value Object) 被称为贫血模型,只有行为的对象,就是框架分层中常见的Logic/Service/Manager层(对应到EJB2中的Stateless Session Bean)

1
以应用层 Service 使用 DAO、PO 基础设施包装业务逻辑的开发方式,乍一看以为应用层是在对领域建模的实现,”领域层“有着丰富的对象链接,和真正的领域模型也非常类似,但当我们代码随着业务功能逻辑的逐步实现中会慢慢发现,我们写了一堆的 get/set 对象,而他们被反复交叉使用,没有与任何领域聚合,也就是不具有任何的行为动作,只是一堆贫血模型对象。 这种反模式的设计,其实完全与面向对象的设计是背道而驰的,面向对象的设计更希望行为和数据绑定在一起,与之对比的贫血模型更像是面向过程设计。 在 MVC 分层结构下,所有的行为都被写入到 Service 对象中,最终你会得到一组事务处理的过程脚本,从而完美的避开了领域模型设计所带来的好处(清晰的职责边界、聚合的功能服务、清晰的面向对象)
2、DDD

DDD 的分层结构也是面向对象编程的本质:”一个对象拥有行为和数据“,在领域层包括了:对象、聚合对象、仓储和Service实现。

image-20240218164301895

  • DDD 的分层结构更注重 Domain 领域层的实现,由很薄的应用层定义接口和编排接口,由领域层做具体的实现。
  • 所有的业务逻辑都按照各自的职责边界拆分成一块块的功能领域,每一个功能领域都是充血模型的结构的具体实现。
  • 那么这样的代码最终实现以后,无论在迭代、维护、人员更替,都能很好按照领域设计文档找到对应的代码实现进行开发

三、设计原则

首先 DDD 的设计分为战略和战术;

  • 战略设计:从业务视角出发,建立业务领域模型、划分职责边界,建立通用语言的界限上下文。顶层战略设计构建的领域模型结构,是整个服务后期编排的重点,它确定了功能的职责边界、聚合、对象等,也就绝对了后期服务战术实现的开发和交付质量。重视战略,才能落地好战术!
  • 战术设计:从技术视角出发,侧重于领域模型的技术实现,完成功能开发和交付落地。领域设计的重点包括:实体、聚合对象、值对象、领域服务、仓储,还有一个非常重点的设计模式。任何一个较为复杂的领域模型实现都需要考虑设计模式的使用,否则即使战略优秀,战术也能干回 MVC 去。

在以DDD领域驱动设计落地的过程中,要依靠领域驱动设计的设计思想,通过事件风暴建立领域模型,合理划分领域逻辑和物理边界,建立领域对象及服务矩阵和服务架构图,定义符合DDD分层架构思想的代码结构模型,保证业务模型与代码模型的一致性。通过上述设计思想、方法和过程,指导团队按照DDD设计思想完成微服务设计和开发。

  1. 拒绝泥球小单体、拒绝污染功能与服务、拒绝加功能排期一个月
  2. 架构出高可用极易符合互联网高速迭代的应用服务
  3. 物料化、组装化、可编排的服务,提高人效
  4. 要领域驱动设计,而不是数据驱动设计,也不是界面驱动设计
  5. 要职能清晰的分层,而不是什么都放的大箩筐
1
DDD 的领域模型设计,界限内的上下文,可以拆分为独立的微服务。但不仅要从业务视角看问题,也要考虑非业务的技术因素,包括:高性能、安全、团队、技术异构等,这些非业务的技术因素,也会决定领域模型落地的具体落地。

三、领域模型

一、关键词解释

1
1.领域 领域其实就是我们的范围,而范围实际上就是我们的边界,我们做什么,做到什么程度,最低多少 ,最高多少。 领域其实就是我们的范围,而范围实际上就是我们的边界,我们做什么,做到什么程度,最低多少 ,最高多少。 比如 电商 就是一个领域 ,金融是一个领域,教育是一个领域,你要确定你做的软件产品,具体的领域边界,分析涉及到的数据,业务规则,流程,然后通过面向对象的方式建立一个模型,再选择合适的技术实现。 2.子域 领域太过于复杂,业务太过于分散,这个时候我们做一个拆分,把领域划分成子域,比如电商系统很复杂,将他拆分成比如一个个的模块,订单,商品,库存,甚至我们的模块还能进行细分,比如库存,可以分为本地库,异地库,三方托管库。划分子域 1 是分治,2是可以对子域进行分析,重要的子域加资源。 3.核心域(子域) 业务核心,核心的竞争力,因为企业愿景不同,领域愿景也不同,核心域也不同,说白了,就是你们项目最初立项的目的是什么,目标是什么。为什么要做这个项目。核心域的范围并不一定是一次就能确认的,可能需要迭代很多次,每一次都有可能扩大或缩小。 4.通用域(子域) 整个领域都能够用到的子域,比如我们的认证,权限等等相关的模块,这就是我们的通用域。 5.支撑域(子域) 支撑域实际上就是不包含核心竞争力的功能,也不包含通用的功能,但是又是必须的支撑。 6.通用语言 必须要有一个东西能够让我们的团队人员交流起来有一个标准。并且他还需要能够正确的,简单的,清晰的表达业务。让我们的技术专家,业务人员,产品,测试,架构都能够达成共识,并且协同合作。这个叫做通用语言,类似需求文档里的词语解释,就是让大家能在同一维度进行高效的交流沟通。 7.限界上下文 限界和上下文。限界就是领域的边界,也就是范围,而上下文则是语义环境。通过领域的限界上下文,我们就可以在统一的领域边界内用统一的语言进行交流。 8.领域模型 领域模型是对领域内的概念类或现实世界中对象的可视化表示,领域模型是用来描述业务对象之间的引用关系。 业务角色,业务角色表示的是一个角色承担的一系列责任。比如收银员,他的责任是计算商品价格,收钱,找零,甚至退换货。 业务实体,业务实体表示的是其实你使用或者可交付的工件,资源,事件。比如电商项目中的商品。你需要给卖家打印的发票。 业务用例,实际上业务用例显示的是协作角色与业务实体之间如何执行工作流程,也就是我们的业务链路 实体(Entity) 正常的实体对象,有唯一键标识,就算所有字段内容一样也不是一个,例如学生,id,姓名,年龄,就算姓名年龄一样,也不是一个。 9.值对象(Value Object) 没有唯一性标识,例如 学生实体上有一个地址,这个地址是一个对象,里面字段为,省,市,街道,详细地址,这个地址信息对象就是值对象。通过对象属性值来识别的对象,它将多个相关属性组合为一个概念整体: 业务形态:值对象只是若干个属性的集合,只有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑。 代码形态:值对象在代码中有这样两种形态。如果值对象是单一属性,则直接定义为实体类的属性;如果值对象是属性集合,则把它设计为 Class 类,Class 将具有整体概念的多个属性归集到属性集合,这样的值对象没有 ID,会被实体整体引用。 运行形态:引用单一属性的值对象或只有一条记录的多属性值对象的实体数据库形态: 10.聚合聚合根 聚合根是聚合的管理者,在聚合内部负责协调实体和值对象按照固定的业务规则协同完成共同的业务逻辑。聚合根是实体,有实体的特点,具有全局唯一标识,有独立的生命周期。一个聚合只有一个聚合根,聚合根在聚合内对实体和值对象采用直接对象引用的方式进行组织和协调,聚合根与聚合根之间通过 ID 关联的方式实现聚合之间的协同。 平常我们有的实体比较复杂,为了满足业务,有可能会弄出一个非常复杂的实体,里面包含很多实体,跟这个类似,这里的聚合是指同一生命周期,同一业务域的实体聚合成一个聚合根,也就是最外面的实体,并且这个实体管理者里面的所有实体,外面只能通过聚合根进行任何和请求,聚合根再对里面进行操作,这里我感觉就是高内聚,聚合根不易过大。 11.领域服务 业务逻辑写那个实体中都不合适就写到领域服务中 注意: 不能在领域服务里写太多的业务逻辑 不能在领域服务里调用dao和仓储 领域服务视为实体的一种补充,里面的属性和逻辑也和实体一样,不能是基础类型得是含有业务含义的值对象 ·领域服务之间可以进行调用,最好是保持父域的领域服务调用子域的领域服务,不允许跨越上下文进行调用 和实体一样,如果有不相关的领域服务的调用,或者有跨上下文的情况,使用领域事件进行解耦 12.领域事件 领域事件是领域模型中非常重要的一部分,用来表示领域中发生的事件。一个领域事件将导致进一步的业务操作,在实现业务解耦的同时,还有助于形成完整的业务闭环,在做用户场景分析时,我们要捕捉业务、需求人员或领域专家口中的关键词:“如果发生……,则……”“当做完……的时候,请通知……”“发生……时,则……”等。在这些场景中,如果发生某种事件后,会触发进一步的操作,那么这个事件很可能就是领域事件。事件驱动,有一些代码逻辑,例如下单,后面有很多操作,一般我们一个下单方法里会包含好几个子方法,并且带有if,时间长了会非常冗余,并且方法越来越大,事件驱动就是,当下单之后发一个事件(mq)如果需要做一些操作,多个端接收这个消息事件,然后做自己的操作就行,扩展的话就多一个接收方,对之前代码无侵入修改。 领域专家所关心的发生在领域中的一些事件。 将领域中所发生的活动建模成一系列的离散事件。每个事件都用领域对象来表示...领域事件是领域模型的组成部分,表示领域中所发生的事情。 13.数据持久化对象 (Persistent Object, PO) 与数据库结构一一映射,它是数据持久化过程中的数据载体。 14.领域对象( Domain Object, DO) 微服务运行时核心业务对象的载体, DO 一般包括实体或值对象。 15.数据传输对象( Data Transfer Object, DTO) 用于前端应用与微服务应用层或者微服务之间的数据组装和传输,是应用之间数据传输的载体。 16.视图对象(View Object, VO) 用于封装展示层指定页面或组件的数据。 17.工厂(Factories) 创建复杂对象,隐藏创建细节 18.仓储(Repository) 提供查找和持久化对象的方法。

二、领域模型分类

1、失血模型
1
失血模型实际上就是我们的对象模型中只包含我们的get,set方法,像我们的排序,分页等等任何的通用性操作都不会包含在我们的对象中。

优点

  • 领域对象结构简单

缺点

  • 肿胀的业务服务代码逻辑,难于理解和维护
  • 无法良好的应对复杂业务逻辑和场景
2、贫血模型
1
贫血模型实际上就是在失血模型的基础上增加了不依赖于持久化的原子领域逻辑。比如我们经常会使用到的排序,分页等等操作。

优点

  • 层次结构清楚,各层之间单向依赖
  • 对于只有少量业务逻辑的应用来说,使用起来非常自然
  • 开发迅速,易于理解

缺点

  • 无法良好的应对非常复杂逻辑和场景
3、充血模型
1
充血模型中实际上我们的领域对象不仅仅包含了我们的非持久化逻辑,甚至还包含了我们的持久化逻辑.那么我们的业务逻辑层在充血模型里还需不需要呢?依然是需要的,但是我们的充血模型的业务层实际上只会做一些整合的操作以及封装事务,其他的操作都会在我们的领域对象中完成

优点

  • 更加符合OO的原则
  • Business Logic层很薄,符合单一职责,不像在贫血模型里面那样包含所有的业务逻辑太过沉重,只充当事务管理以及整合的角色,不和DAO打交道。

缺点

  • 职责不好进行划分,我们的业务逻辑到底是划分到我们的哪一个层级呢?是领域对象还是业务属性呢?哪些划分到领域对象呢?而且持久化的内容都放在我们的领域对象里.所以我们在写业务的时候会深入到领域对象去,这对于开发者的水平要求很高,变相的增加了企业的成本.
  • 其次,由于充血模型包含了太多的操作,你实例化的时候也会有很大的麻烦,拿到了很多你不需要的关联模型.
4、胀血模型
1
胀血模型取消掉了service层,所有业务逻辑的整合都放在了领域模型中,简化了分层架构,也算是符合面向对象设计原则,取消了业务逻辑层,直接在domain object封装事务和授权,可能会授权很多原本不属于这个领域对象的逻辑,从而影响模型不稳定。代码的稳定性和维护的稳定性。

三、关系图

1、DDD与中台的关系

image-20240218161822635

2、各层的概念以及关系

image-20240218161912185

3、DDD和中台的之间的关系

image-20240218161931103

4、三层架构 VS DDD

image-20240218162000891

image-20240219093512911

5、领域事件

image-20240219093044046

6、领域驱动设计的核心原则

image-20240219093151397

image-20240219093203946