当前位置: 首页 > article >正文

DDD架构实战 领域层 事件驱动

目录

核心实现:

这种实现方式的优势:

在实际项目中,你可能需要:


事件驱动往往是在一个微服务内部实现的

领域时间是DDD架构中比较常见的概念

在领域层内部的一个模型更改了状态或者发生了一些行为

向外发送一些通知

这些通知叫做领域时间

类似于MQ

这个通知不会在分布式系统内部传递

只会在单个微服务内部传递

和MQ一样 这样能解耦

我们可以通过发布事件的方式进行一种松耦合的通信

以下是DDD架构中领域层事件驱动的Java实现示例,展示如何通过领域事件实现松耦合的业务逻辑:

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;/*** 领域事件接口* 所有领域事件需实现此接口*/
interface DomainEvent {UUID getEventId();LocalDateTime getOccurredOn();
}/*** 领域事件抽象基类* 提供事件ID和发生时间的基础实现*/
abstract class BaseDomainEvent implements DomainEvent {private final UUID eventId;private final LocalDateTime occurredOn;protected BaseDomainEvent() {this.eventId = UUID.randomUUID();this.occurredOn = LocalDateTime.now();}@Overridepublic UUID getEventId() {return eventId;}@Overridepublic LocalDateTime getOccurredOn() {return occurredOn;}
}/*** 订单已创建事件*/
class OrderCreatedEvent extends BaseDomainEvent {private final String orderId;private final String customerId;public OrderCreatedEvent(String orderId, String customerId) {this.orderId = orderId;this.customerId = customerId;}public String getOrderId() {return orderId;}public String getCustomerId() {return customerId;}
}/*** 订单已支付事件*/
class OrderPaidEvent extends BaseDomainEvent {private final String orderId;private final LocalDateTime paymentTime;public OrderPaidEvent(String orderId, LocalDateTime paymentTime) {this.orderId = orderId;this.paymentTime = paymentTime;}public String getOrderId() {return orderId;}public LocalDateTime getPaymentTime() {return paymentTime;}
}/*** 库存扣减事件*/
class InventoryDeductedEvent extends BaseDomainEvent {private final String productId;private final int quantity;public InventoryDeductedEvent(String productId, int quantity) {this.productId = productId;this.quantity = quantity;}public String getProductId() {return productId;}public int getQuantity() {return quantity;}
}/*** 领域事件发布者接口*/
interface DomainEventPublisher {void publish(DomainEvent event);
}/*** 领域事件订阅者接口*/
interface DomainEventSubscriber<T extends DomainEvent> {void handleEvent(T event);Class<T> subscribedToEventType();
}/*** 领域事件总线(单例实现)* 负责事件的注册、发布和分发*/
class DomainEventBus implements DomainEventPublisher {private static final DomainEventBus INSTANCE = new DomainEventBus();private final List<DomainEventSubscriber<?>> subscribers = new ArrayList<>();private DomainEventBus() {}public static DomainEventBus getInstance() {return INSTANCE;}public <T extends DomainEvent> void register(DomainEventSubscriber<T> subscriber) {subscribers.add(subscriber);}@Overridepublic void publish(DomainEvent event) {subscribers.stream().filter(subscriber -> subscriber.subscribedToEventType().isInstance(event)).forEach(subscriber -> dispatch(event, subscriber));}@SuppressWarnings("unchecked")private <T extends DomainEvent> void dispatch(DomainEvent event, DomainEventSubscriber<?> subscriber) {// 同步处理事件((DomainEventSubscriber<T>) subscriber).handleEvent((T) event);// 异步处理示例:// CompletableFuture.runAsync(() -> //     ((DomainEventSubscriber<T>) subscriber).handleEvent((T) event)// );}
}/*** 领域事件聚合根接口* 定义聚合根应具备的事件管理能力*/
interface EventSourcedAggregateRoot {List<DomainEvent> getDomainEvents();void clearDomainEvents();
}/*** 订单实体(聚合根)* 集成领域事件功能*/
class Order implements EventSourcedAggregateRoot {private final String id;private final String customerId;private OrderStatus status;private final List<DomainEvent> domainEvents = new ArrayList<>();public Order(String id, String customerId) {this.id = id;this.customerId = customerId;this.status = OrderStatus.CREATED;// 创建订单时发布订单创建事件registerEvent(new OrderCreatedEvent(id, customerId));}public void pay() {if (status != OrderStatus.CREATED) {throw new IllegalStateException("订单状态错误,当前状态:" + status);}this.status = OrderStatus.PAID;// 支付时发布订单已支付事件registerEvent(new OrderPaidEvent(id, LocalDateTime.now()));}private void registerEvent(DomainEvent event) {domainEvents.add(event);}@Overridepublic List<DomainEvent> getDomainEvents() {return new ArrayList<>(domainEvents);}@Overridepublic void clearDomainEvents() {domainEvents.clear();}// Getterspublic String getId() {return id;}public String getCustomerId() {return customerId;}public OrderStatus getStatus() {return status;}
}/*** 库存服务(领域服务)* 处理库存相关业务逻辑*/
class InventoryService implements DomainEventSubscriber<OrderPaidEvent> {private final InventoryRepository inventoryRepository;public InventoryService(InventoryRepository inventoryRepository) {this.inventoryRepository = inventoryRepository;}@Overridepublic void handleEvent(OrderPaidEvent event) {// 订单支付后扣减库存// 实际应用中需要根据订单ID查询订单详情获取商品信息// 这里简化处理System.out.println("处理订单支付事件:订单ID=" + event.getOrderId());// 扣减库存逻辑// inventoryRepository.deductStock(productId, quantity);// 发布库存扣减事件DomainEventBus.getInstance().publish(new InventoryDeductedEvent("P001", 1));}@Overridepublic Class<OrderPaidEvent> subscribedToEventType() {return OrderPaidEvent.class;}
}/*** 库存仓储接口*/
interface InventoryRepository {void deductStock(String productId, int quantity);
}/*** 库存仓储实现(示例)*/
class InMemoryInventoryRepository implements InventoryRepository {@Overridepublic void deductStock(String productId, int quantity) {System.out.println("扣减库存:商品ID=" + productId + ",数量=" + quantity);// 实际实现会更新数据库中的库存记录}
}/*** 订单服务(应用服务)* 协调领域对象和事件处理*/
class OrderApplicationService {private final OrderRepository orderRepository;private final DomainEventPublisher eventPublisher;public OrderApplicationService(OrderRepository orderRepository, DomainEventPublisher eventPublisher) {this.orderRepository = orderRepository;this.eventPublisher = eventPublisher;}public void createOrder(String customerId, List<OrderItem> items) {// 创建订单String orderId = UUID.randomUUID().toString();Order order = new Order(orderId, customerId);// 持久化订单orderRepository.save(order);// 发布领域事件publishEvents(order);}public void payOrder(String orderId) {// 获取订单Order order = orderRepository.findById(orderId).orElseThrow(() -> new IllegalArgumentException("订单不存在"));// 执行支付业务逻辑order.pay();// 持久化更新orderRepository.save(order);// 发布领域事件publishEvents(order);}private void publishEvents(Order order) {order.getDomainEvents().forEach(eventPublisher::publish);order.clearDomainEvents();}
}/*** 订单仓储接口*/
interface OrderRepository {void save(Order order);java.util.Optional<Order> findById(String orderId);
}/*** 主程序演示*/
public class Main {public static void main(String[] args) {// 初始化组件InventoryRepository inventoryRepository = new InMemoryInventoryRepository();InventoryService inventoryService = new InventoryService(inventoryRepository);OrderRepository orderRepository = new InMemoryOrderRepository();DomainEventPublisher eventPublisher = DomainEventBus.getInstance();// 注册事件订阅者DomainEventBus.getInstance().register(inventoryService);// 初始化订单应用服务OrderApplicationService orderService = new OrderApplicationService(orderRepository, eventPublisher);// 创建订单orderService.createOrder("C001", Collections.emptyList());// 支付订单(触发事件)orderService.payOrder("ORD12345");}
}    

这个示例展示了DDD领域层事件驱动的

核心实现:

  1. 领域事件模型
    • 定义了DomainEvent接口和基础实现类BaseDomainEvent
    • 创建了具体领域事件:OrderCreatedEventOrderPaidEventInventoryDeductedEvent
  1. 事件总线机制
    • DomainEventBus作为单例事件总线,负责事件的注册和分发
    • 支持同步/异步事件处理模式
  1. 事件发布与订阅
    • 聚合根(Order)内部管理和发布事件
    • 领域服务(InventoryService)通过实现DomainEventSubscriber接口订阅特定事件
  1. 事件驱动的业务流程
    • 订单支付(Order.pay())触发OrderPaidEvent
    • 库存服务订阅该事件并执行库存扣减
    • 库存扣减后发布InventoryDeductedEvent供其他服务订阅
  1. 应用服务协调
    • OrderApplicationService负责事务管理和事件发布
    • 确保业务操作完成后发布所有领域事件

这种实现方式的优势:

  • 业务逻辑解耦:订单和库存服务无需直接依赖
  • 可扩展性:新增业务逻辑只需添加新的事件订阅者
  • 一致性保障:通过事件机制保证最终一致性
  • 可测试性:领域事件可作为测试断言的一部分

在实际项目中,你可能需要:

  1. 添加事件持久化机制,确保事件不丢失
  2. 实现事件重试和补偿机制
  3. 考虑使用消息队列(如RabbitMQ/Kafka)实现跨服务事件
  4. 添加事件版本控制和序列化机制

相关文章:

DDD架构实战 领域层 事件驱动

目录 核心实现&#xff1a; 这种实现方式的优势&#xff1a; 在实际项目中&#xff0c;你可能需要&#xff1a; 事件驱动往往是在一个微服务内部实现的 领域时间是DDD架构中比较常见的概念 在领域层内部的一个模型更改了状态或者发生了一些行为 向外发送一些通知 这些通…...

c# List<string>.Add(s) 报错:UnsupportedOperationException

在使用c#读取目录下指定格式文件目录后&#xff0c;使用List<string>.Add 来保存文件名时&#xff0c;出现UnsupportedOperationException错误&#xff0c;找了好久不知道问题出在哪里。 以下是错误代码&#xff1a; using (var fbd new FolderBrowserDialog{Descripti…...

postman基础

前言 本次 Chat 将结合业界广为推崇和使用的 RestAPI 设计典范 Github API&#xff0c;详细介绍 Postman 接口测试工具的使用方法和实战技巧。 在开始这个教程之前&#xff0c;先聊一下为什么接口测试在现软件行业如此重要&#xff1f; 为什么我们要学习 Postman&#xff1f;…...

python训练营day45

知识点回顾&#xff1a; tensorboard的发展历史和原理tensorboard的常见操作tensorboard在cifar上的实战&#xff1a;MLP和CNN模型 效果展示如下&#xff0c;很适合拿去组会汇报撑页数&#xff1a; 作业&#xff1a;对resnet18在cifar10上采用微调策略下&#xff0c;用tensorbo…...

B+树知识点总结

核心目标&#xff1a;减少磁盘 I/O 数据库系统&#xff08;如 MySQL&#xff09;的主要性能瓶颈通常在于磁盘 I/O&#xff08;读取和写入数据到物理硬盘的速度远慢于内存访问&#xff09;。B 树的设计核心就是最大限度地减少访问数据时所需的磁盘 I/O 次数。 一、B 树的基本结…...

Halcon透视矩阵

在 Halcon中&#xff0c;透视变换矩阵用于将图像从一个视角转换到另一个视角&#xff0c;常用于图像校正和几何变换。以下是计算透视变换矩阵的步骤及代码示例。 透视形变图像校正的步骤 对图像左简单的处理&#xff0c;分割要校正的区域&#xff1b;提取区域的顶点坐标信息&…...

SpringCloud——OpenFeign

概述&#xff1a; OpenFeign是基于Spring的声明式调用的HTTP客户端&#xff0c;大大简化了编写Web服务客户端的过程&#xff0c;用于快速构建http请求调用其他服务模块。同时也是spring cloud默认选择的服务通信工具。 使用方法&#xff1a; RestTemplate手动构建: // 带查询…...

007-nlohmann/json 项目应用-C++开源库108杰

本课为 fswatch&#xff08;第一“杰”&#xff09;的示例项目加上对配置文件读取的支持&#xff0c;同时借助 第三“杰” CLI11 的支持&#xff0c;完美实现命令行参数与配置文件的逻辑统一。 012-nlohmann/json-4-项目应用 项目基于原有的 CMake 项目 HelloFSWatch 修改。 C…...

移动端测试岗位高频面试题及解析

文章目录 一、基础概念二、自动化测试三、性能测试四、专项测试五、安全与稳定性六、高级场景七、实战难题八、其他面题 一、基础概念 移动端测试与Web测试的核心区别&#xff1f; 解析&#xff1a;网络波动&#xff08;弱网测试&#xff09;、设备碎片化&#xff08;机型适配&…...

gvim比较两个文件不同并合并差异

使用 gvim 比较两个文件的不同&#xff1a; 方式一&#xff0c;使用 gvim 同时打开两个待比较的文件。 比较通用方式是采用 gvim -d 选项&#xff0c;具体命令&#xff0c;如下&#xff1a; gvim -d <file1> <file2>方式二&#xff0c;先用 gvim 打开一个文件&am…...

App使用webview套壳引入h5(二)—— app内访问h5,顶部被手机顶部菜单遮挡问题,保留顶部安全距离

引入webview的页面添加safeAreaInsets&#xff0c;对weview的webviewStyles做处理 在myApp中改造 entry.vue代码如下 template><view class"entry-page" :style"{ paddingTop: safeAreaInsets.top px }"><web-view :webview-styles"we…...

Git GitHub Gitee

一、Git 是一个免费、开源的分布式版本控制系统。 版本控制&#xff1a;一种记录文件内容变化&#xff0c;以便将来查阅特定版本修订情况的系统。它最重要的就是可以记录文件修改历史记录&#xff0c;从而让用户可以看历史版本&#xff0c;方便版本切换。 1.和集中式版本控制…...

《深度体验 Egg.js:打造企业级 Node.js 应用的全景指南》

&#x1f680; 核心亮点&#xff1a;Koa 的二次觉醒 企业级基因&#xff1a;阿里多年双十一验证的框架稳定性插件化架构&#xff1a;config.plugins 实现功能模块即插即用渐进式演进&#xff1a;从 50 行代码到 5 万行代码的无缝扩容能力 &#x1f527; 实战配置解析&#xff…...

蓝桥杯2118 排列字母

问题描述 小蓝要把一个字符串中的字母按其在字母表中的顺序排列。 例如&#xff0c;LANQIAO 排列后为 AAILNOQ。 又如&#xff0c;GOODGOODSTUDYDAYDAYUP 排列后为 AADDDDDGGOOOOPSTUUYYY。 请问对于以下字符串&#xff0c;排列之后字符串是什么&#xff1f; WHERETHEREIS…...

Python应用break初解

大家好!作为 Python 初学者&#xff0c;控制循环的执行是编程中的基础技能之一。在本文中&#xff0c;我们将深入探讨break语句的用途和用法&#xff0c;帮助您更好地理解和掌握这一强大的工具。 定义: break是 Python 中的一个保留关键字&#xff0c;用于在循环中提前终止循环…...

PLSQLDeveloper配置OracleInstantClient连接Oracle数据库

PL/SQLDeveloper配置Oracle Instant Client连接Oracle数据库 文章目录 PL/SQLDeveloper配置Oracle Instant Client连接Oracle数据库 1. Oracle Instant Client下载与配置1. Oracle Instant Client下载2. Oracle Instant Client解压配置1. 解压2. 配置 2. PL/SQL Developer下载、…...

高股息打底+政策催化增强+永续经营兜底

通过分析农业银行&#xff08;政策红利高股息&#xff09;与长江电力&#xff08;垄断资源现金流堡垒&#xff09;的共性&#xff0c;提炼出以下策略框架&#xff1a; ​​1. 核心筛选标准​​ • 高股息防御性&#xff1a;股息率>3%&#xff0c;分红率稳定&#xff08;40%…...

双电机差速控制的MATLAB Simulink仿真方案,使用PWM和PID调节实现360°转向与速度控制_可复现,有问题请联系博主

以下是一个双电机差速控制的MATLAB Simulink仿真方案,使用PWM和PID调节实现360转向与速度控制。方案包含系统建模、控制策略和仿真实现。 系统模型 差速运动学模型: 线速度 ( v = \frac{v_r + v_l}{2} )角速度 ( \omega = \frac{v_r - v_l}{d} )其中 ( v_r, v_l ) 为右/左轮线…...

【Oracle】触发器

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 触发器基础概述1.1 触发器的概念与特点1.2 触发器的分类1.3 触发器的执行顺序 2. DML触发器2.1 基础DML触发器2.1.1 INSERT触发器2.1.2 UPDATE触发器2.1.3 DELETE触发器 2.2 高级DML触发器2.2.1 复合触发器2…...

基于深度学习的无人机轨迹预测

完整代码见文末 随着无人机技术的不断发展&#xff0c;无人机在农业、物流、监控等领域的应用日益广泛。精准的轨迹预测不仅能够提高无人机飞行的效率和安全性&#xff0c;还能在应对复杂环境下的突发状况时做出迅速反应。因此&#xff0c;基于深度学习的无人机轨迹预测已成为…...

git连接本地仓库以及gitee

参考:gitee创建新仓库并上传代码_gitee新建仓库导入代码-CSDN博客 git初始化以及添加git分支 在idea查看master主分支 报错 原因gitee推送更新失败问题记录&#xff1a;remote: error: hook declined to update refs/heads/master-CSDN博客 取消邮箱暴露...

使用Python和OpenCV实现图像识别与目标检测

在计算机视觉领域&#xff0c;图像识别和目标检测是两个非常重要的任务。图像识别是指识别图像中的内容&#xff0c;例如判断一张图片中是否包含某个特定物体&#xff1b;目标检测则是在图像中定位并识别多个物体的位置和类别。OpenCV是一个功能强大的开源计算机视觉库&#xf…...

麒麟v10系统的docker重大问题解决-不支持容器名称解析

今天给客户在麒麟v10Kylin-Server-V10-SP1下安装nextcloudonlyoffice的时候出现无法连接onlyoffice的问题,经过分析找到了是docker版本过低的原因,现在把解决思路和步骤分享给大家。 一、问题 用一键安装工具,给客户装好了系统,Nextcloud可以正常访问 但是访问nextcloud中的o…...

基于5G下行信号的模糊函数分析matlab仿真,对比速度模糊函数和距离模糊函数

目录 1.引言 2.算法仿真效果演示 3.数据集格式或算法参数简介 4.MATLAB部分程序 5.算法涉及理论知识概要 6.参考文献 7.完整算法代码文件获得 1.引言 模糊函数&#xff08;Ambiguity Function, AF&#xff09;是信号处理领域用于分析信号时频分辨能力的核心工具&#xf…...

Selenium自动下载浏览器驱动

为什么需要自动下载浏览器驱动&#xff1f; 血泪场景重现 新人入职第一天&#xff1a; 花3小时配置Chrome/Firefox驱动版本不匹配导致SessionNotCreatedException 浏览器自动更新后&#xff1a; 所有测试脚本突然崩溃手动查找驱动耗时长 终极解决方案&#xff1a;自动下载驱…...

数据库优化实战分享:高频场景下的性能调优技巧与案例解析

在实际开发与生产运维中&#xff0c;数据库的性能瓶颈往往是影响系统响应速度和用户体验的关键因素。尤其是在高并发访问、海量数据处理、复杂查询逻辑等高频场景下&#xff0c;数据库优化不仅仅是“锦上添花”&#xff0c;更是“雪中送炭”。本篇博文将结合实际项目经验&#…...

Redis 过期了解

Redis 版本&#xff1a;5.0 &#xff1a; 一&#xff1a;过期监听&#xff1a; Spring Data Redis 封装了 Redis 的 Pub/Sub 功能&#xff0c;提供了对 key 过期事件的监听支持。 1. 核心类&#xff1a;KeyExpirationEventMessageListener 这个抽象类是 Spring 提供的&#x…...

微信小程序前端面经

一、技术栈与编码能力&#xff08;10min&#xff09; 1. Vue 3 & Composition API Q1&#xff1a;请解释一下 ref 和 reactive 的区别&#xff1f;你在项目中是如何使用的&#xff1f; 答&#xff1a;ref是包装一个原始值或对象&#xff0c;通过.value访问&#xff0c;r…...

android 之 Tombstone

Android 系统中的 Tombstone 是记录 Native 层崩溃信息的关键日志文件&#xff0c;当应用或系统服务因严重错误&#xff08;如内存访问异常、空指针解引用等&#xff09;崩溃时自动生成。以下是其核心机制与分析方法详解&#xff1a; 一、Tombstone 的生成机制 触发条件 当 Na…...

六级作文模板笔记

旧模板 Today there is a growing awareness that mental well-being needs to be given as much attention as physical health. In the contemporary world where change is constant and knowledge is ever-expanding, mental well-being has become increasingly importan…...