关于ddd:DDD概念复杂难懂实际落地如何设计代码实现模型

6次阅读

共计 7407 个字符,预计需要花费 19 分钟才能阅读完成。

明天我想与你聊一聊,DDD 概念简单、难懂,理论落地该怎么设计代码实现模型。对于这个话题,先说说整体框架、思路,我打算联合两局部分享给你,每一部分,置信认真看完,都会或多或少有所播种。以下内容,预计 1 分钟左右可疾速看完:

前一部分,办法篇,旨在具体介绍 DDD 所蕴含的几个外围概念,以及围绕这些概念所构建的 DDD 代码实现模型的组成构造。

后半局部,实际篇,进一步思考。我持续接着说,承接后面的内容,要想让这些代码实现模型真正落地,咱们须要把它们与具体的利用场景联合起来。我将偏重具体论述 DDD 代码实现模型的设计办法,并给出一个具体的案例剖析。

随同着业务零碎复杂度的一直晋升,以及微服务架构等分布式技术体系的大行其道,畛域驱动设计(Domain Driven Design,DDD),日渐成为零碎建模畛域的支流设计思维和模式。在 DDD 中,引入了限界上下文、聚合、实体、值对象、畛域事件、资源库、应用服务等一系列外围概念。

通过这些概念,开发人员能够发展零碎设计和实现工作。然而,DDD 中的这些概念绝对都比拟形象,甚至有些艰涩难懂。再往相通或相似问题点上靠,我认为本质上对于简单难懂的概念的了解和把握,咱们一开始不用过于纠结这些概念自身,而是能够把它们与事实中的具体实现模型对应起来。

通过两者之间的正当映射,来促成对概念自身的了解,如下图所示。这里多说一句,即使你是其余技术畛域的敌人,或者也曾遇到过相似问题,并有着共通性。心愿看完明天的分享,能够或多或少帮忙到你,并有所启发、思考。

在上图中,咱们一方面尝试把简单概念映射到实现模型。另一方面,基于对实现模型的把握,能够反推对简单概念的了解水平,从而更好地把握这些概念。这也更足以见得实际能力出真知,也只有设计过实现模型,能力真正把握这些概念,从而把它们利用到各种具体的场景中。

这是一种卓有成效的方法。

那么问题就来了,在日常开发过程中,如何确保 DDD 真正落地,把这些抽象概念转为具体代码模式,是咱们明天要探讨的内容。

01⎪

想设计代码实现模型,咱非得理解 DDD 中这几个外围概念?

总体来说,DDD 提供的是一种 开展业务建模和软件设计的方法论。DDD 认为良好的零碎架构,应该是技术架构和业务架构互相交融的后果,开发人员不能脱离业务畛域来设计技术架构。为了实现这一指标,DDD 提出了一组外围概念,如图 1 所示。

咱们先来看第一个外围概念,就比拟难于了解,即限界上下文(Boundary Context)。在 DDD 中,当咱们把业务畛域拆分成多个子域之后,限界上下文明确了子域的 业务界线,并实现子域与子域之间的隔离,如图 2 所示。

有了限界上下文,咱们就须要围绕业务场景设计畛域模型对象(Domain Model Object)。畛域模型对象,蕴含了 丰盛的业务逻辑和操作行为,这点和只蕴含数据属性的传统数据对象,有本质区别。

因而,畛域模型对象是咱们在利用 DDD 时,最应该关注的一组对象,也是最难把握的一组对象。

在 DDD 中,畛域模型对象包含三大类,即聚合(Aggregate)、实体(Entity)和值对象(Value Object),这三类对象各有特点。

相较畛域模型对象,畛域事件诞生较晚,但也是畛域模型的一个重要组成部分,因为事实中很多场景,都能够形象成事件(Event),如图 4。

在 DDD 中,通过畛域事件能够实现 业务状态变动的无效流传,并在单个限界上下文外部或在多个上下文之间,对这些状态变动做出响应。

业务畛域中的各种状态变动最终都须要进行存储。为此,DDD 提供了一个针对业务数据的 对立拜访入口,这就是资源库(Repository)。通过资源库,咱们能够实现对各种畛域对象的长久化操作,如图 5 所示。

最初,咱们来引入应用服务的概念。应用服务包含 命令(Command)服务和查问(Query)服务 两大类,实质上起到的是一种解耦和协调作用,确保各种畛域模型对象之间的交互和合作。因而,在 波及到多个限界上下文之间的交互 时,咱们须要重点关注应用服务。如图 6 所示。

02⎪

概念简单又难懂,想理论业务场景下真正落地,需引入 DDD 代码实现模型

对于 DDD 中的外围概念,我就简略介绍到这里,下一步就是要探讨一个所有开发人员都必须面对的话题,即如何将这些简单难懂的概念,在事实的开发过程中可能真正落地?这就须要引入 DDD 代码实现模型。

▶︎ 要想设计代码实现模型,先得搞清楚它有哪几局部组成?

无论设计办法有多好,可能转换为可运行的代码才是王道,这点对于 DDD 而言尤为如此。

惋惜的是,目前业界对于如何施行这些概念,并没有一套对立的规范和标准,这就导致咱们在具体的开发过程中,经常感到无从下手。为此,本文 专门提炼了一整套 DDD 代码实现模型。接下来,让咱们从 DDD 代码实现模型的基本概念和组织构造展开讨论。

▶︎ 在讲代码实现模型之前,先弄清楚什么是实现模型

说起模型(Model),业界支流的方法论认为存在三种不同的类型,即畛域模型、设计模型和代码模型,如图 7 所示。

对于畛域模型,咱们在后面的内容中曾经做了介绍。在 DDD 中,聚合、实体、值对象、畛域事件等,都能够归属到这一模型的领域。

而设计模型(Design Model),能够分成边界模型和外部模型两个组成部分。边界模型明确零碎边界,形象系统集成和交互计划。而外部模型细化边界模型,在明确零碎边界的前提下,实现零碎外部模块和组件的形象和构建。因而,在 DDD 中,咱们往往 从限界上下文的角度登程,来发展设计模型的建设,如下图所示。

最初,代码模型为事实世界的解决方案,提供可执行的零碎环境。咱们能够通过在畛域模型和设计模型中嵌入代码的形式来构建代码模型,该模型是将DDD 各个简单概念转换为可执行代码的关键所在,也是咱们明天要探讨的次要内容。

显然,畛域模型、设计模型和代码模型之间,存在一种档次依赖关系,如图 9 所示。

首先,畛域模型代表畛域的固有业务;

设计模型指向畛域模型,关注对外部接口的承诺以及交互关系;

代码模型提供了残缺实现过程,是对设计模型的细化。

正是通过这三种 模型的整合,实现了从事实问题到最终可能落地的实现计划的演进。

▶︎ DDD 代码实现模型,应蕴含哪些局部?

针对 DDD 代码实现模型的探讨,咱们也将遵循上述三种模型的整合过程。联合 DDD 中的各种外围概念,咱们梳理 DDD 代码实现模型组成构造,如图 10 所示。

在上图中,咱们能够清晰看到 DDD 代码实现模型的四个组成部分,别离面向畛域对象、应用服务、基础设施以及上下文集成。讲到这里,你可能会问,为什么咱们要这样设计 DDD 的代码实现模型呢?

咱们晓得一个残缺的 DDD 应用程序,通常由多个限界上下文形成。因而,对于代码实现模型而言,咱们须要 重点思考两个维度,即:

  • 单个限界上下文实现过程中的代码模型
  • 多个限界上下文之间集成过程中的代码模型

在上图中,对于畛域对象、应用服务、基础设施代码实现模型的探讨,属于单个限界上下文的领域,而上下文实现代码集成模型,显然面向多个限界上下文,如图 11 所示。

通过后面内容的学习,置信你对 DDD 代码实现模型的组成构造,已了然在胸。

那么,在日常开发过程中,咱们应该如何设计这些代码实现模型呢?有没有具体的案例能够参考呢?这几个问题点,你能够先停下来推敲下。

03⎪ 如何设计 DDD 代码实现模型?

在剖析 DDD 代码实现模型时,对于上一篇提到的四个组成部分,咱们须要梳理它们的 代码构造和依赖关系。针对代码构造,咱们须要明确代码包的组成,以及外部所蕴含的技术组件。

在明确了包构造之后,依赖关系指的是咱们须要进一步明确这些代码包和技术组件之间的交互关系。基于这两点,让咱们先来探讨畛域对象的代码实现模型。

▶︎ 畛域对象代码实现模型

针对畛域对象,咱们通常用“domain”这个单词,对代码包构造的顶层包进行命名,在该包构造下的所有技术组件,都属于畛域对象的领域。

具体而言,在 DDD 中,畛域对象包含畛域模型对象、畛域事件、资源库以及应用服务所波及到的命令和查问对象,其中畛域模型对象能够分为聚合、实体和值对象这三大类。

因而,在 DDD 所有的代码实现模型中,畛域对象 波及的代码构造最为简单 ,能够分成 两个档次,如图 1 所示。

图 1

能够看到,这里的“domain”代表整个畛域对象,而“model”则代表畛域模型对象,请留神这两者在命名上的区别,以及它们之间的从属关系。畛域对象是 DDD 代码实现模型的根底,蕴含外围业务逻辑的实现。

▶︎ 应用服务代码实现模型

相似地,针对应用服务,咱们通常应用“application”来命名顶层包构造。应用服务蕴含查问服务和命令服务这两大类,所以在子包的命名上,也会用“commandservice”和“queryservice”加以辨别,如图 2 所示。

图 2

显然,命令服务和查问服务,别离依赖于畛域对象代码实现模型中的命令对象和查问对象,咱们用虚线示意这层依赖关系。在 DDD 的代码实现模型中,应用服务能够说是交互关系最为简单的一个代码模型。

一方面,它须要将命令和查问操作,分派给聚合对象等畛域模型对象。

另一方面,它也须要别离和基础设施,以及其余限界上下文进行交互。

对于后者,咱们在探讨到案例剖析时,还会做进一步开展。

▶︎ 基础设施代码实现模型

其实,所谓的基础设施,指的是 DDD 应用程序中所应用到的各种具体技术、工具和框架。常见的基础设施类组件次要包含这几个方面:

  • 数据长久化(Persistence)
  • 音讯通信(Messaging)
  • 系统配置(Config)
  • 安全控制(Security)

因而,基础设施的包构造 并不是固定的,而是依据具体的技术开发要求进行灵便的组织,这里给出一个常见的包构造,如图 3 所示。针对基础设施,咱们应用了“infrastructure”,对这一包构造进行命名。

图 3 上图中 有一点须要留神,代表数据长久化的“persistence”包,和代表音讯通信的“messaging”包,在基础设施代码实现模型中是最常见的,因为它们别离对应着畛域对象中的资源库和畛域事件。

在 DDD 中,资源库和畛域事件的定义位于畛域对象代码实现模型中,它们与具体的实现技术无关。而与具体实现技术相干的长久化和音讯通信,则位于基础设施代码实现模型中。这里体现了 畛域对象与实现技术互相拆散的设计准则。

▶︎ 上下文集成代码实现模型

最初,咱们来探讨上下文集成代码实现模型。须要留神的是 ,这个 模型实现起来难度最大,因为波及到多种系统集成技术体系。

针对这一代码实现模型,咱们首先须要明确它是面向多个限界上下文的,所以咱们须要思考数据的流向,也就是所谓的 外向(Inbound)数据和内向(Outbound)数据。

一方面,限界上下文,须要裸露拜访入口供其余上下文进行应用。站在以后上下文角度看,这是一个 Inbound 操作。而当某一个上下文向内部上下文发动申请时,这就是一个 Outbound 操作,如图 4 所示。

图 4

在代码实现模型的设计上,咱们也将采纳“inbound”和“outbound”来命名包构造。那么 这两个包构造下,应该蕴含哪些技术组件呢?

咱们先来探讨“outbound”包构造,如图 5 所示。图中,“rest”包中的 REST API 将内部申请,转化为外部的 Command 和 Query 对象,并交由应用服务进行解决。在这个转化过程中,通常须要引入专门的 DTO(Data Transfer Object,数据传输对象)对象,和组装器(Assembler)对象。

图 5

同时,“eventpublisher”包中的 事件公布器(Event Publisher),则用来面向内部限界上下文公布畛域事件。

接着,咱们探讨“inbound”包构造。在一个限界上下文中,数据的 Inbound 操作次要有两类,一类是 防腐层(Anti-Corruption Layer,ACL),用来向近程 REST API 发动申请并获取后果。另一类是用来实现对畛域事件进行响应的 事件处理器(Event Handler),如图 6 所示。

图 6

基于上下文集成过程,两个上下文中的“inbound”和“outbound”包构造中所蕴含的技术组件,实际上是一一对应的,如图 7 所示。

能够看到,一个限界上下文“inbound”中的“acl”和“eventhandler”,别离对应着另一个限界上下文“outbound”中的“rest”和“eventpublisher”。

图 7

至此,对于 DDD 中四大类代码实现模型,已介绍完。在接下来的内容中,咱们将 基于一个具体的利用场景,通过案例剖析,将这些代码实现模型付诸于实际。基于这个案例,你能够将本文后面介绍的所有内容,和日常开发过程分割起来,进一步把握将模型转化为具体代码的实现办法和技巧。

04⎪ DDD 代码实现模型案例剖析

在事实世界中,工单解决是一个十分常见的业务需要。而工单的发动,通常都是因为用户须要对订单进行征询或投诉。

在这个场景中,基于 DDD 的设计办法,咱们能够别离拆分收工单(Ticket)、客服(Staff),以及订单(Order)这三个限界上下文。在这三个上下文中,Ticket 上下文,会别离与 Staff 和 Order 这两个上下文进行集成,从而创立工单申请,如图 8 所示。

请留神,图中展现了 Ticket 上下文,所具备的两种不同的上下文集成形式。

针对 Staff 上下文,Ticket 上下文将应用 REST API,实现对工单中客服数据的获取。

而针对 Order 上下文,则应用了畛域事件,即一旦 Order 的状态发生变化,Order 上下文会发送对应的畛域事件到 Ticket 上下文中。

图 8

▶︎ Ticket 上下文代码实现模型示例

显然,针对这一场景,Ticket 上下文同时具备了 Inbound 和 Outbound 操作。因而,它的代码实现模型是最残缺的,如图 9 所示。

图 9

上图中,咱们应用 IDEA 这款开发工具和 Spring Boot 这一特定的开发框架,构建了 Ticket 限界上下文的代码实现模型。咱们能够很清晰地看到,DDD 四种代码实现模型的表现形式,就是五个顶层的代码包构造。其中,上下文集成代码实现模型同时蕴含了“inbound”和“outbound”这两个代码包。

咱们再对这些顶层代码包构造做开展,能够失去如图 10 所示的子代码包构造。

图 10(高低滑动查看)

上图所示的所有子代码包构造,在后面的内容中也都曾经给出了相应的形容,这里便不再赘述。

Ticket 上下文中,命令服务 TicketCommandService 实现了对 Staff 服务的上下文集成,这时候采纳的是防腐层 ACL 组件,示例代码如下所示。

能够看到,这里应用 AclStaffService 这个 ACL 组件,对 Staff 服务发动了近程调用,而后把返回后果填充到命令对象,并创立 Ticket 聚合。最终,咱们通过 TicketRepository 实现了对聚合对象的长久化操作。

图 11

上述 AclStaffService,就实现了对 Staff 上下文所提供的 REST API 的调用,示例代码如下所示。这里用到了 Spring 自带的 RestTemplate 模板工具类,实现对近程 HTTP 端点的拜访操作。

图 12

▶︎ Staff 上下文代码实现模型示例

在 Staff 上下文,咱们须要实现对上述 REST API 的构建,它的代码工程构造如下图所示。

能够看到,相较 Ticket 上下文,Staff 上下文的代码构造比较简单,因为该上下文只须要提供对外的“outbound”包,而基础设施局部也只须要实现对畛域对象的长久化操作即可。

图 13

▶︎ Order 上下文代码实现模型示例

最初,咱们来到 Order 限界上下文,它的代码实现模型是这样的,能够一起看下。

图 14

咱们晓得 Order 上下文,提供了针对 Order 数据的畛域事件公布机制,所以它的“outbound”包中蕴含了用于公布畛域事件的“eventpublisher”子包,并提供了一个 OrderEventPublisherService,如下所示。

图 15

这里通过 Spring Cloud Stream,实现了畛域事件的公布。而在 Ticket 上下文中,咱们同样能够基于 Spring Cloud Stream,实现对该畛域事件的监听和生产,示例代码如下所示。

图 16

请留神,上述 OrderUpdatedEventHandler,位于 Ticket 上下文“inbound”包的”eventhandler”子包中。

对于这些具体实现代码的解说不是本文的重点,你能够参考笔者在 Github 上的案例代码进行零碎学习:https://github.com/tianminzhe…。

05⎪ 总结和延长思考

明天的分享到这里就完结了。本文内容具体答复了开发人员,在实现 DDD 应用程序中所碰到的一个外围问题,即如何构建 DDD 的代码实现模型。之所以要探讨这个话题,起因在于 DDD 中的很多概念都比拟艰涩难懂,而业界也没有为如何实现这些概念,提供对立的开发标准和规范。

而通过将 DDD 中的各种简单概念与具体代码实现模型进行映射,在帮咱们更好地了解这些概念的同时,也可能将它们间接利用到日常开发过程中。

通过本文内容的介绍,开发人员能够联合本身的业务开发需要,设计一套残缺的 DDD 代码实现模型。这里也附上全文思维导图,助你回顾、梳理思路等。

图 17 全文思维框架导图 - 帮忙你疾速回顾、梳理、总结

▶︎ 最初,我感觉还是有必要强调一点

本文中给出的 DDD 代码实现模型,也只是一个参考模型。而代码实现模型的设计,也与具体所采纳的技术体系有肯定关联。在本文所展现的案例中,咱们应用了 Spring Boot、Spring Cloud Stream 等 Spring 家族中的开发框架,来开发 DDD 应用程序。

而如果你应用 Axon 这种基于事件溯源模式的 DDD 开发框架,那么在代码实现模型中,就须要引入用于事件散发和存储的 Gateway、EventStore 等组件,而位于基础设施中的传统数据长久化组件,可能就不肯定会被应用到。

当然,基于咱们明天介绍的内容,置信你并不难对这套 DDD 代码实现模型进行扩大。DDD 作为一种零碎建模方法论,也存在一些诸如分层架构、整洁架构、六边形架构等多种架构格调。

针对每种架构格调,咱们都须要设计对应的代码实现模型。

而基于本文中介绍的内容,通过对 DDD 中各个外围概念与实现模型之间进行正当的映射,我在文中 提供了一套设计代码实现模型的零碎办法,从而帮忙你能够应答不同架构格调的实现要求。

这也是本文的外围价值所在。

正文完
 0