一、概念:
在事件风暴时,除了命令和操作等业务行为意外,还有一种非常重要的事件,这种事件发生后通常会导致进一步的业务操作,在DDD中被称为领域事件。
二、如何识别领域事件:
在事件风暴(三种方式:用例分析、场景分析、用户旅程分析)过程中,捕捉业务、需求人员或领域专家口中的关键词:“如果发生。。。,则。。。” “当做完。。。的时候,请通知。。。” 等。在这些场景中,如果发生某种事件后,会触发进一步的操作,那么这个事件很可能就是领域事件。
三、领域事件的类型和相应的处理方式:
1、微服务内的领域事件
当领域事件发生在微服务内的聚合之间,领域事件发生后完成事件实体构建和事件数据持久化,发布方聚合将事件发布到事件总线,订阅方接收事件数据完成后续业务操作。
微服务内应用服务,可以通过跨聚合的服务编排和组合,以服务调用的方式完成跨聚合的访问,这种方式通常应用于实时性和数据一致性要求高的场景。这个过程会用到分布式事务,以保证发布方和订阅方的数据同时更新成功。
2、 微服务之间的领域事件
跨微服务的领域事件会在不同的限界上下文或领域模型之间实现业务协作,其主要目的是实现微服务解耦,减轻微服务之间实时服务访问的压力。
跨微服务的事件机制要总体考虑事件构建、发布和订阅、事件数据持久化、消息中间件,甚至事件数据持久化时还可能需要考虑引入分布式事务机制等。
微服务之间的访问也可以采用应用服务直接调用的方式,实现数据和服务的实时访问,弊端就是跨微服务的数据同时变更需要引入分布式事务,以确保数据的一致性。分布式事务机制会影响系统性能,增加微服务之间的耦合,所以我们还是要尽量避免使用分布式事务。
四、领域事件相关案例:
事件起点:客户购买保险 - 业务人员完成保单录入 - 生成投保单 - 启动缴费动作。
step 1. 投保微服务生成缴费通知单,发布第一个事件:缴费通知单已生成,将缴费通知单数据发布到消息中间件。收款微服务订阅缴费通知单事件,完成缴费操作。缴费通知单已生成,领域事件结束。
step 2. 收款微服务缴费完成后,发布第二个领域事件:缴费已完成,将缴费数据发布到消息中间件。原来的订阅方收款微服务这时则变成了发布方。原来的事件发布方投保微服务转换为订阅方。投保微服务在收到缴费信息并确认缴费完成后,完成投保单转成保单的操作。缴费已完成,领域事件结束。
step 3. 投保微服务在投保单转保单完成后,发布第三个领域事件:保单已生成,将保单数据发布到消息中间件。保单微服务接收到保单数据后,完成保单数据保存操作。保单已生成,领域事件结束。
step 4. 保单微服务完成保单数据保存后,后面还会发生一系列的领域事件,以并发的方式将保单数据通过消息中间件发送到佣金、收付费和再保等微服务,一直到财务,完后保单后续所有业务流程。这里就不详细说了。
总之,通过领域事件驱动的异步化机制,可以推动业务流程和数据在各个不同微服务之间的流转,实现微服务的解耦,减轻微服务之间服务调用的压力,提升用户体验。
五、领域事件总体架构
领域事件处理包括:事件构建和发布、事件数据持久化、事件总线、消息中间件、事件接收和处理等。
说明:
1、事件构建和发布:
(1)事件属性
a、基本属性:事件唯一标识、发生时间、事件类型和事件源
解释:
- 事件唯一标识应该是全局唯一的,以便事件能够无歧义地在多个限界上下文中传递。
- 事件基本属性主要记录事件自身以及事件发生背景的数据。
b、业务属性:用于记录事件发生那一刻的业务数据,这些数据会随着事件传输到订阅方,以开展下一步的业务操作
(2)事件实体
a、事件基本属性和业务属性一起构成事件实体,事件实体依赖聚合根。
b、领域事件发生后,事件中的业务数据不再修改,因此业务数据可以以序列化值对象的形式保存,这种存储格式在消息中间件中也比较容易解析和获取。
注:为了保证事件结构的统一,应该创建事件基类(DomainEvent)
(3)事件发布的注意事项:
- 事件发布之前需要先构建事件实体并持久化。
- 事件发布的方式:
a、可以通过应用服务或者领域服务发布到事件总线或者消息中间件
b、也可以从事件表中利用定时程序或数据库日志捕获技术获取增量事件数据,发布到消息中间件。
2、事件数据持久化
(1)为什么要持久化?
- 事件数据持久化可用于系统之间的数据对账,或者实现发布方和订阅方事件数据的审计。
- 当遇到消息中间件、订阅方系统宕机或者网络中断,在问题解决后仍可继续后续业务流转,保证数据的一致性。
(2)持久化的方案:
- 持久化到本地业务数据库的事件表中,利用本地事务保证业务和事件数据的一致性。
- 持久化到共享的事件数据库中。这里需要注意的是:业务数据库和事件数据库不在一个数据库中,它们的数据持久化操作会跨数据库,因此需要分布式事务机制来保证业务和事件数据的强一致性,结果就是会对系统性能造成一定的影响。
3、事件总线
(1)概念:事件总线是实现微服务内聚合之间领域事件的重要组件,它提供事件分发和接收等服务。
(2)特点:事件总线是进程内模型,它会在微服务内聚合之间遍历订阅者列表,采取同步或异步的模式传递数据。
(3)事件分发流程大致如下:
- 如果是微服务内的订阅者(其它聚合),则直接分发到指定订阅者;
- 如果是微服务外的订阅者,将事件数据保存到事件库(表)并异步发送到消息中间件;
- 如果同时存在微服务内和外订阅者,则先分发到内部订阅者,将事件消息保存到事件库(表),再异步发送到消息中间件。
4、消息中间件
实现跨微服务的事件发布和订阅,比如Kafka \ RabbitMQ
5、事件接受和处理
微服务订阅方在应用层采用监听机制,接收消息队列中的事件数据,完成事件数据的持久化后,就可以开始进一步的业务处理。领域事件处理可在领域服务中实现。
六、领域事件运行机制的一个例子
这个领域事件发生在投保和收款微服务之间。发生的领域事件是:缴费通知单已生成。下一步的业务操作是:缴费。
事件起点:出单员生成投保单,核保通过后,发起生成缴费通知单的操作。
1. 投保微服务应用服务,调用聚合中的领域服务 createPaymentNotice 和 createPaymentNoticeEvent,分别创建缴费通知单、缴费通知单事件。其中缴费通知单事件类 PaymentNoticeEvent 继承基类 DomainEvent。
2. 利用仓储服务持久化缴费通知单相关的业务和事件数据。为了避免分布式事务,这些业务和事件数据都持久化到本地投保微服务数据库中。
3. 通过数据库日志捕获技术或者定时程序,从数据库事件表中获取事件增量数据,发布到消息中间件。这里说明:事件发布也可以通过应用服务或者领域服务完成发布。
4. 收款微服务在应用层从消息中间件订阅缴费通知单事件消息主题,监听并获取事件数据后,应用服务调用领域层的领域服务将事件数据持久化到本地数据库中。
5. 收款微服务调用领域层的领域服务 PayPremium,完成缴费。
6.事件结束
注意:缴费完成后,后续流程的微服务还会产生很多新的领域事件,比如缴费已完成、保单已保存等等。这些后续的事件处理基本上跟 1~6 的处理机制类似。