当前位置: 首页 > 科技观察

DDD的概念复杂且难以理解,实践中如何设计代码实现模型?_0

时间:2023-03-13 19:38:01 科技观察

写在前面:今天继续和大家聊聊,DDD的概念复杂难懂,实践中如何设计代码实现模型。可能你刚看到这部分的内容,觉得这里需要多说一点,这个话题,在框架上,分为两个部分:方法+实践。第一部分是方法。目的是详细介绍DDD中包含的几个核心概念,以及围绕这些概念构建的DDD代码实现模型的组成结构。至于为什么一定要讲,我在上一篇文章开头已经跟大家说的很清楚了。另外,考虑到有的朋友可能是刚点进来,没看过之前的文章,或者还没来得及看,所以我在这里也指出分享这个话题的必要性,希望对大家有所帮助你很快就会知道自己能或多或少在哪些方面有所收获。开门见山,遗憾的是,目前业界并没有一套统一的标准和规范,如何去落实这些理念,这让我们在具体的开发过程中常常感到无所适从。为此,本文特地提炼出一套DDD代码实现模型。另外,让我再说一件事关于看的方式。基于更新和发布分为前后两部分,这个涉及到先后顺序。如果还没有看过上一篇文章的小伙伴们,建议先花几分钟时间,或者结合文章中大小标题思路的引导,对下一篇文章的框架有个大概的了解。我们可以把上一篇做完,再进入本文的分享,结合起来,效果会更好。01如何设计DDD代码实现模型?在分析DDD代码实现模型的时候,我们需要梳理一下上一篇文章提到的四个组件的代码结构和依赖关系。对于代码结构,我们需要明确代码包的组成以及其中包含的技术组件。明确了包结构之后,依赖指的是我们需要进一步明确这些代码包与技术组件之间的交互。基于这两点,我们先来讨论领域对象的代码实现模型。??领域对象代码实现模型对于领域对象,我们通常用“领域”一词来命名代码包结构的顶层包。包结构下的所有技术组件都属于领域对象的范畴。具体来说,在DDD中,领域对象包括领域模型对象、领域事件、资源库以及应用服务中涉及的命令和查询对象。领域模型对象可以分为三类:聚合、实体和值对象。因此,在DDD的所有代码实现模型中,领域对象涉及的代码结构是最复杂的,可以分为两个层次,如图1所示。从图1可以看出,这里的“领域”代表整个领域对象,而“模型”代表领域模型对象。请注意两者在命名上的区别以及它们之间的隶属关系。领域对象是DDD代码实现模型的基础,包括核心业务逻辑的实现。??应用服务代码实现模型同样,对于应用服务,我们通常使用“application”来命名顶层包结构。应用服务包括查询服务和命令服务两大类,因此在命名上也使用“commandservice”和“queryservice”来区分子包,如图2所示。如图2所示,命令服务和查询服务依赖于命令对象和查询对象分别在域对象代码实现模型中,我们用虚线表示这种依赖程度。在DDD的代码实现模型中,应用服务可以说是交互关系最为复杂的代码模型。一方面,它需要将命令和查询操作分派给聚合对象等领域模型对象。另一方面,它还需要分别与基础设施和其他有界上下文进行交互。关于后者,我们将在讨论案例研究时进一步展开。??Infrastructure代码实现模型其实所谓的infrastructure是指DDD应用中使用的各种具体的技术、工具和框架。常见的基础设施组件主要包括这几个方面:数据持久化(Persistence)、消息通信(Messaging)、系统配置(Config)、安全控制(Security)。因此,基础设施的包结构不是固定的,而是取决于具体的技术开发,需要灵活组织。这里是一个常见的包结构,如图3所示。对于infrastructure,我们使用“infrastructure”来命名这个包结构。图3上图中需要注意的一点是,代表数据持久化的“persistence”包和代表消息通信的“messaging”包在基础设施代码实现模型中是最常见的,因为它们对应的是领域对象。存储库和域事件。在DDD中,资源库和领域事件的定义位于领域对象代码实现模型中,与具体的实现技术无关。具体实现技术相关的持久化和消息通信位于基础代码实现模型中。这体现了领域对象和实现技术相互分离的设计原则。??上下文集成代码实现模型最后,我们来讨论一下上下文集成代码实现模型。需要注意的是,这种模式是最难实现的,因为它涉及到多种系统集成技术体系。对于这个代码实现模型,我们首先需要明确的是它是面向多个有界上下文的,所以我们需要考虑数据的流向,也就是所谓的入站(Inbound)数据和出站(Outbound)数据。一方面,有界上下文需要公开访问条目以供其他上下文使用。从当前上下文来看,这是一个入站操作。而当一个context向外部context发起请求时,这就是一个outbound操作,如图4所示。图4在代码实现模型的设计中,我们也会使用“inbound”和“outbound”来命名包结构。那么这两个包结构下应该包含哪些技术组件呢?我们先来讨论“outbound”包结构,如图5所示。图中,“rest”包中的RESTAPI将外部请求转换为内部的Command和Query对象,提交给应用服务进行处理。在这个转换过程中,通常需要引入特殊的DTO(DataTransferObject,数据传输对象)对象和汇编器(Assembler)对象。图5同时,“eventpublisher”包中的事件发布者(EventPublisher)用于向外部限界上下文发布领域事件。接下来,我们讨论“入站”数据包结构。在有界上下文中,有两种主要类型的入站数据操作。一种是反腐败层(Anti-CorruptionLayer,ACL),用于向远程RESTAPI发起请求并获取结果。另一类是事件处理器(EventHandler),用于完成对领域事件的响应,如图6所示。图6是基于上下文的集成流程。两个上下文中的“inbound”和“outbound”包结构中包含的技术组件实际上是一一对应的,如图7所示。可以看出,“acl”和“eventhandler”在一个bounded上下文“inbound”对应于另一个有界上下文“outbound”中的“rest”和“eventpublisher”。图7至此,DDD中的四类代码实现模型已经介绍完毕。在接下来的内容中,我们将结合具体的应用场景,通过案例分析将这些代码实现模型付诸实践。基于此案例,您可以将本文介绍的所有内容与日常开发流程联系起来,进一步掌握将模型转化为具体代码的实现方法和技巧。02DDD代码实现模型案例分析在现实世界中,工单处理是一个非常普遍的业务需求。工单的发起通常是因为用户需要咨询或投诉订单。在这种场景下,基于DDD的设计方式,我们可以分离出三个限界上下文:Ticket、Staff、Order。在这三个上下文中,Ticket上下文将分别与Staff和Order这两个上下文集成,创建一个工单应用,如图8所示。请注意,图中显示的是Ticket上下文,它有两种不同的上下文集成方式.对于Staffcontext,Ticketcontext会使用RESTAPI完成工单中客服数据的获取。对于Ordercontext,使用的是领域事件,即一旦Order的状态发生变化,Ordercontext会将相应的领域事??件发送给Ticketcontext。图8??Ticketcontext代码实现模型示例显然,对于这个场景,Ticketcontext既有Inbound操作,也有Outbound操作。因此,它的代码实现模型是最完整的,如图9所示。在图9的上图中,我们使用IDEA这个开发工具,以及SpringBoot这个具体的开发框架,构建了Ticket的代码实现模型有界上下文。我们可以清楚的看到,四种DDD代码实现模型的表示就是五种顶层代码包结构。其中,上下文集成代码实现模型包括“入站”和“出站”两种代码包。我们再对这些顶层代码包结构进行展开,得到如图10所示的子代码包结构。图10(上下滑动查看)上图中所有的子代码包结构都已经给出了相应的说明在前面的内容中,这里不再赘述。在Ticket上下文中,命令服务TicketCommandService完成Staff服务的上下文集成。这时候就用到了防腐层ACL组件。示例代码如下。可以看出,这里使用了ACL组件AclStaffService来发起对Staff服务的远程调用,然后将返回结果填充到command对象中,并创建一个Ticket聚合。最后我们通过TicketRepository完成了聚合对象的持久化操作。上述图11中的AclStaffService完成了对Staffcontext提供的RESTAPI的调用,示例代码如下。这里使用Spring自带的RestTemplate模板工具类来完成对远程HTTP端点的访问操作。图12??Staffcontext代码实现模型示例在Staffcontext中,我们需要完成上述RESTAPI的构建,其代码工程结构如下图所示。可以看出,与Ticket上下文相比,Staff上下文的代码结构更简单,因为上下文只需要对外提供“出站”包,基础设施部分只需要完成领域对象的持久化操作.图13??Ordercontext代码实现模型示例最后我们来到Orderboundedcontext,它的代码实现模型是这样的,大家可以一起看看。在图14中,我们知道了Order上下文,并提供了Order数据的领域事件发布机制,因此其“outbound”包中包含了发布领域事件的“eventpublisher”子包,并提供了一个OrderEventPublisherService,如下所示。这里的图15通过SpringCloudStream实现领域事件的发布。在Ticket上下文中,我们也可以基于SpringCloudStream来监听和消费该字段的事件。示例代码如下。图16请注意,上面的OrderUpdatedEventHandler位于Ticket上下文“inbound”包的“eventhandler”子包中。这些具体实现代码的解释不是本文的重点。可以参考作者在Github上的案例代码进行系统学习:https://github.com/tianminzheng/customer-service。03总结与延伸思考今天的分享到此结束。本文内容详细解答了开发者在实现DDD应用中遇到的一个核心问题,即如何构建DDD代码实现模型。之所以讨论这个话题,是因为DDD中的很多概念都比较晦涩,业界也没有对这些概念如何实现提供统一的开发规范和标准。通过将DDD中各种复杂的概念与具体的代码实现模型进行映射,在帮助我们更好地理解这些概念的同时,也可以将它们直接应用到日常的开发过程中。通过本文内容的介绍,开发者可以根据自己的业务发展需求,设计出一套完整的DDD代码实现模型。这里还附上全文思维导图,帮助大家回顾整理思路。图17全文思维框架图——助你快速回顾、梳理、总结??最后,我认为有必要强调一下,本文给出的DDD代码实现模型只是一个参考模型。代码实现模型的设计也与采用的具体技术体系有关。本文案例中,我们使用SpringBoot、SpringCloudStream等Spring家族的开发框架开发DDD应用。而如果使用Axon这种基于事件源模型的DDD开发框架,那么在代码实现模型中,需要引入Gateway、EventStore等组件进行事件分发和存储,而传统的数据持久化组件在基础设施中,可能不一定被使用。当然,根据我们今天的介绍,相信大家扩展这套DDD代码实现模型并不困难。DDD作为一种系统建模方法论,也有分层架构、整洁架构、六边形架构等多种架构风格。对于每一种架构风格,我们都需要设计相应的代码实现模型。基于本文介绍的内容,通过DDD中各个核心概念与实现模型的合理映射,本文提供了一套系统的代码实现模型设计方法,以帮助大家应对不同的建筑风格要求。这也是本文的核心价值。