【DDD】全网最详细2万字讲解DDD,从理论到实战(代码示例) 2

简介: 【DDD】全网最详细2万字讲解DDD,从理论到实战(代码示例)

领域事件相关案例

我来给你介绍一个保险承保业务过程中有关领域事件的案例。

一个保单的生成,经历了很多子域、业务状态变更和跨微服务业务数据的传递。这个过程会产生很多的领域事件,这些领域事件促成了保险业务数据、对象在不同的微服务和子域之间的流转和角色转换。在下面这张图中,我列出了几个关键流程,用来说明如何用领域事件驱动设计来驱动承保业务流程。

6825e8a12d42470c88165dc10fe1bf76.png事件起点:客户购买保险 - 业务人员完成保单录入 - 生成投保单 - 启动缴费动作。

总之,通过领域事件驱动的异步化机制,可以推动业务流程和数据在各个不同微服务之间的流转,实现微服务的解耦,减轻微服务之间服务调用的压力,提升用户体验。


一个完整的领域事件 = 事件发布 + 事件存储 + 事件分发 + 事件处理。


事件发布:构建一个事件,需要唯一标识,然后发布;

事件存储:发布事件前需要存储,因为接收后的事件也会存储,可用于重试或对账等;就是每次执行一次具体的操作时,把行为记录下来,执行持久化。

事件分发:服务内的应用服务或者领域服务直接发布给订阅者,服务外需要借助消息中间件,比如Kafka,RabbitMQ等,支持同步或者异步。

事件处理:先将事件存储,然后再处理。

当然了,实际开发中事件存储和事件处理不是必须的。


因此实现方案:发布订阅模式,分为跨上下文(kafka,RocketMq)和上下文内(spring事件,Guava Event Bus)的领域事件。


用户注册后,发送短信和邮件,使用spring事件实现领域事件代码如下:

/**
 * 用户注册事件
 * @Author WDYin
 **/
public class UserRegisterEvent extends ApplicationEvent {
    public UserRegisterEvent(Object source) {
        super(source);
    }
}
/**
 * 用户监听事件
 * @Author WDYin
 **/
@Component
public class UserListener {
    @EventListener(UserRegisterEvent.class)
    public void userRegister(UserRegisterEvent event) {
        User user = (User) event.getSource();
        System.out.println("用户注册。。。发送短信。。。" + user);
        System.out.println("用户注册。。。发送邮件。。。" + user);
    }
    @EventListener(UserCancelEvent.class)
    public void userCancelEvent(UserCancelEvent event) {
        User user = (User) event.getSource();
        System.out.println("用户注销。。。" + user);
    }
}
/**
 * 发布用户注册事件
 * @Author : WDYin
 */
@RunWith(value = SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = DemoApplication.class)
public class MyClient {
    @Autowired
    private ApplicationContext applicationContext;
    @Test
    public void test() {
        User user = new User();
        //发布事件
        applicationContext.publishEvent(new UserRegisterEvent(user));
    }
}

事件风暴

DDD分层架构

DDD 的分层架构在不断发展。最早是传统的四层架构;再后来领域层和应用层之间增加了上下文环境(Context)层,五层架构(DCI)就此形成了。

用户接口层

用户接口层是前端应用和微服务之间服务访问和数据交换的桥梁。它处理前端发送的Restful 请求和解析用户输入的配置文件等,将数据传递给应用层。或获取应用服务的数据后,进行数据组装,向前端提供数据服务。主要服务形态是 Facade 服务。Facade 服务分为接口和实现两个部分。完成服务定向,DO 与 DTO 数据的转换和组装,实现前端与应用层数据的转换和交换。


①一般包括用户接口、Web 服务、rpc请求,mq消息等外部输入均被视为外部输入的请求。对外暴露API,具体形式不限于RPC、Rest API、消息等。

②一般都很薄,提供必要的参数校验和异常捕获流程。

③一般会提供VO或者DTO到Entity或者ValueObject的转换,用于前后端调用的适配,当然dto可以直接使用command和query,视情况而定。

④用户接口层很重要,在于前后端调用的适配。若你的微服务要面向很多应用或渠道提供服务,而每个渠道的入参出参都不一样,你不太可能开发出太多应用服务,这样Facade接口就起很好的作用了,包括DO和DTO对象的组装和转换等。

应用层

应用层是很薄的一层,理论上不应该有业务规则或逻辑,主要面向用例和流程相关的操作。但应用层又位于领域层之上,因为领域层包含多个聚合,所以它可以协调多个聚合的服务和领域对象完成服务编排和组合,协作完成业务操作。除了同步方法调用外,还可以发布或者订阅领域事件,权限校验、事务控制,一个事务对应一个聚合根。


应用层负责不同聚合之间的服务和数据协调,负责微服务之间的事件发布和订阅。通过应用服务对外暴露微服务的内部功能,这样就可以隐藏领域层核心业务逻辑的复杂性以及内部实现机制。应用层的主要服务形态有:应用服务、事件发布和订阅服务。应用服务内用于组合和编排的服务,主要来源于领域服务,也可以是外部微服务的应用服务。

领域层

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

这里我要特别解释一下其中几个领域对象的关系,以便你在设计领域层的时候能更加清楚。首先,领域模型的业务逻辑主要是由实体和领域服务来实现的,其中实体会采用充血模型来实现所有与之相关的业务功能。其次,你要知道,实体和领域对象在实现业务逻辑上不是同级的,当领域中的某些功能,单一实体(或者值对象)不能实现时,领域服务就会出马,它可以组合聚合内的多个实体(或者值对象),实现复杂的业务逻辑。


领域层主要的服务形态有实体方法和领域服务。实体采用充血模型,在实体类内部实现实体相关的所有业务逻辑,实现的形式是实体类中的方法。实体是微服务的原子业务逻辑单元。在设计时我们主要考虑实体自身的属性和业务行为,实现领域模型的核心基础能力。不必过多考虑外部操作和业务流程,这样才能保证领域模型的稳定性。


①包含了业务核心的领域模型:实体(聚合根+值对象),使用充血模型实现所有与之相关的业务功能,主要表达业务概念,业务状态信息以及业务规则。

②真正的业务逻辑都在领域层编写,聚合根负责封装实现业务逻辑,对应用层暴露领域级别的服务接口。

③聚合根不能直接操作其它聚合根,聚合根与聚合根之间只能通过聚合根ID引用;同限界上下文内的聚合之间的领域服务可直接调用;两个限界上下文的交互必须通过应用服务层抽离接口->适配层适配。

④跨实体的状态变化,使用领域服务,领域服务不能直接修改实体的状态,只能调用实体的业务方法


DDD 提倡富领域模型,尽量将业务逻辑归属到实体对象上,实在无法归属的部分则设计成领域服务。领域服务会对多个实体或实体方法进行组装和编排,实现跨多个实体的复杂核心业务逻辑。对于严格分层架构,如果单个实体的方法需要对应用层暴露,则需要通过领域服务封装后才能暴露给应用服务

基础层

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


基础层的服务形态主要是仓储服务。仓储服务包括接口和实现两部分。仓储接口服务供应用层或者领域层服务调用,仓储实现服务,完成领域对象的持久化或数据初始化。


比如说,在传统架构设计中,由于上层应用对数据库的强耦合,很多公司在架构演进中最担忧的可能就是换数据库了,因为一旦更换数据库,就可能需要重写大部分的代码,这对应用来说是致命的。那采用依赖倒置的设计以后(说白了就是多套一层接口),应用层就可以通过解耦来保持独立的核心业务


①为业务逻辑提供支撑能力,提供通用的技术能力,仓库写增删改查类似DAO。

② 防腐层实现(封装变化)用于业务检查和隔离第三方服务,内部try catch

架构原则

在《实现领域驱动设计》一书中,DDD 分层架构有一个重要的原则:每层只能与位于其下方的层发生耦合。

而架构根据耦合的紧密程度又可以分为两种:严格分层架构和松散分层架构。优化后的DDD 分层架构模型就属于严格分层架构,任何层只能对位于其直接下方的层产生依赖。而传统的 DDD 分层架构则属于松散分层架构,它允许某层与其任意下方的层发生依赖。那我们怎么选呢?综合我的经验,为了服务的可管理,我建议你采用严格分层架构。


在严格分层架构中,领域服务只能被应用服务调用,而应用服务只能被用户接口层调用,服务是逐层对外封装或组合的,依赖关系清晰。而在松散分层架构中,领域服务可以同时被应用层或用户接口层调用,服务的依赖关系比较复杂且难管理,甚至容易使核心业务逻辑外泄。试想下,如果领域层中的某个服务发生了重大变更,那该如何通知所有调用方同步调整和升级呢?但在严格分层架构中,你只需要逐层通知上层服务就可以了。


3ec889eddf5d4ab181d12a8df132c784.png

a3a7216f695d41b2927c5b4a21736d3a.png

防腐层(ACL)

当某个功能模块需要依赖第三方系统提供的数据或者功能时,我们常用的策略就是直接使用外部系统的API、数据结构。这样存在的问题就是,因使用外部系统,而被外部系统的质量问题影响,从而“腐化”本身设计的问题。


因此我们的解决方案就是在两个系统之间加入一个中间层,隔离第三方系统的依赖,对第三方系统进行通讯转换和语义隔离,这个中间层,我们叫它防腐层

说白了就是,两个系统之间加了中间层,中间层类似适配器模式,解决接口差异的对接,接口转换是单向的(即从调用方向被调用方进行接口转换);防腐层强调两个子系统语义解耦,接口转换是双向的。

服务的调用

微服务内跨层服务调用

微服务架构下往往采用前后端分离的设计模式,前端应用独立部署。前端应用调用发布在API 网关上的 Facade 服务,Facade 定向到应用服务。应用服务作为服务组织和编排者,它的服务调用有这样两种路径:


第一种是应用服务调用并组装领域服务。此时领域服务会组装实体和实体方法,实现核心领域逻辑。领域服务通过仓储服务获取持久化数据对象,完成实体数据初始化。


第二种是应用服务直接调用仓储服务。这种方式主要针对像缓存、文件等类型的基础层数据访问。这类数据主要是查询操作,没有太多的领域逻辑,不经过领域层,不涉及数据库持久化对象。

微服务之间的服务调用

微服务之间的应用服务可以直接访问,也可以通过 API 网关访问。由于跨微服务操作,在进行数据新增和修改操作时,你需关注分布式事务,保证数据的一致性。

领域事件驱动

领域事件驱动包括微服务内和微服务之间的事件。微服务内通过事件总线(EventBus)完成聚合之间的异步处理。微服务之间通过消息中间件完成。异步化的领域事件驱动机制是一种间接的服务访问方式。当应用服务业务逻辑处理完成后,如果发生领域事件,可调用事件发布服务,完成事件发布。当接收到订阅的主题数据时,事件订阅服务会调用事件处理领域服务,完成进一步的业务操作。

服务依赖

DDD 分层架构有一个重要的原则就是:每层只能与位于其下方的层发生耦合。


那根据耦合的紧密程度,分层架构可以分为两种:严格分层架构和松散分层架构。在严格分层架构中,任何层只能与位于其直接下方的层发生依赖。在松散分层架构中,任何层可以与其任意下方的层发生依赖。


在松散分层架构中,领域层的实体方法和领域服务可以直接暴露给应用层和用户接口层。松散分层架构的服务依赖关系,无需逐级封装,可以快速暴露给上层。

但它存在一些问题,第一个是容易暴露领域层核心业务的实现逻辑;第二个是当实体方法或领域服务发生服务变更时,由于服务同时被多层服务调用和组合,不容易找出哪些上层服务调用和组合了它,不方便通知到所有的服务调用方。


严格分层架构可以避免将核心业务逻辑的实现暴露给外部,将实体和方法封装成领域服务,也可以避免在应用层沉淀过多的本该属于领域层的核心业务逻辑,避免应用层变得臃肿。还有就是当服务发生变更时,由于服务只被紧邻上层的服务调用和组合,你只需要逐级告知紧邻上层就可以了,服务可管理性比松散分层架构要好是一定的。

目录
相关文章
|
1月前
|
缓存 前端开发 数据建模
DDD结构学习归纳总结(小傅哥の码场 学习专栏)
DDD结构学习归纳总结(小傅哥の码场 学习专栏)
109 0
|
10月前
|
设计模式 前端开发 关系型数据库
【DDD】全网最详细2万字讲解DDD,从理论到实战(代码示例) 3
【DDD】全网最详细2万字讲解DDD,从理论到实战(代码示例)
2878 2
|
1月前
|
消息中间件 JavaScript Kafka
谈谈代码:DDD从入门到完全入门
之前的DDD文章——谈谈代码:降低复杂度,从放弃三层架构到DDD入门,通篇下来像 是简单的讲了一些概念,然后快速的实战一下——很多同学反馈感觉就是入门了,但没有完全入门,因此我们再加一篇。
209 3
谈谈代码:DDD从入门到完全入门
|
10月前
|
搜索推荐 领域建模 数据库
【DDD】全网最详细2万字讲解DDD,从理论到实战(代码示例) 1
【DDD】全网最详细2万字讲解DDD,从理论到实战(代码示例)
961 0
|
前端开发 架构师 Java
领域驱动设计DDD从入门到代码实践
在本文中,作者将借鉴《实现领域驱动设计》的做法,介绍领域驱动设计的基本概念的同时,用一个虚拟的公司和一个虚拟的项目,把领域驱动设计进行落地实践。
11747 9
领域驱动设计DDD从入门到代码实践
|
程序员 编译器 C++
C++(入门、核心、提高三篇)总结及补充
C++(入门、核心、提高三篇)总结及补充
C++(入门、核心、提高三篇)总结及补充
|
设计模式 SQL 开发框架
DDD开篇
从知道DDD到现在已经很多年了,看了不少理论知识,在项目中也使用了DDD,碰到些问题,也有些思考,整理一下,上升一下,形成一种适合自身的方法论 在回顾过程中,首先追根溯源,什么是DDD?为什么要使用DDD?如何给别人阐述这些最基本的概念与理念,真是个难题
217 0
DDD开篇
|
自然语言处理 安全 架构师
DDD开篇总结
之前写了两篇《DDD开篇》[1]与《DDD应对复杂》[2],是时候总结一下了 对于DDD的启蒙,不管是国内还是国外思维逻辑都是一样的。或者说如果你想写本关于DDD的书,大纲似乎是一样的 首先DDD是什么?给出定义,定义有些抽象,难以一次性接受,那就通过以往问题引出DDD,这时模型、复杂度、开发流程都是自然附带出的概念,再后面就是DDD的知识结构是什么,最后就是讲解一个实例,也有些会把实例穿插到各个篇章中
185 0
DDD开篇总结
领域驱动DDD原理简介与实践(中)
领域驱动DDD原理简介与实践(中)
133 0
领域驱动DDD原理简介与实践(中)