SlimMessageBus实现DDD领域事件:MessageBus.Current静态访问器与聚合根事件发布实战
【免费下载链接】SlimMessageBusLightweight message bus interface for .NET (pub/sub and request-response) with transport plugins for popular message brokers.项目地址: https://gitcode.com/gh_mirrors/sl/SlimMessageBus
SlimMessageBus是 .NET 生态中的轻量级消息总线(Lightweight Message Bus),支持发布/订阅(Pub/Sub)与请求/响应(Request/Response)两种模式。本文以官方 DDD 示例为蓝本,手把手演示如何用 SlimMessageBus 实现DDD 领域事件:聚合根在状态变更后,通过MessageBus.Current静态访问器发布事件,由进程内的 Memory 传输直接分发给事件处理器——全程无需任何外部消息中间件,5 分钟即可跑通。
SlimMessageBus 领域事件的发布订阅架构:一个主题承载多种消息类型
一、为什么用消息总线实现领域事件?
在 DDD(领域驱动设计)中,聚合根状态变更后需要通知审计、物流、通知等多个模块。传统的直接调用会让领域层耦合越来越多的"旁观者",而领域事件将"发生了什么"与"谁来处理"彻底解耦。
用消息总线做领域事件有三个好处:
- 🧩解耦:聚合根只管发布,完全不知道有哪些处理器
- 📮一对多广播:同一条事件可以同时被多个
IConsumer订阅处理 - 🔌可替换传输:今天用进程内 Memory,明天换成 RabbitMQ / Kafka 只改配置不改业务代码
官方文档对这个用例的描述见 docs/UseCases/DomainEvents.md,完整可运行示例在src/Samples/Sample.DomainEvents.WebApi、src/Samples/Sample.DomainEvents.Domain、src/Samples/Sample.DomainEvents.Application三个项目中。
二、MessageBus.Current 静态访问器的工作原理
2.1 解决什么问题?
领域模型(聚合根)通常不依赖 DI 容器,怎么拿到IMessageBus?SlimMessageBus 的答案是一个静态入口——MessageBus.Current,定义在src/SlimMessageBus/MessageBus.cs第 19 行:
public static IMessageBus Current => _provider();它的核心机制是:Current不是单例,而是"当前执行上下文"的代理。
SetProvider(第 14 行)注册一个解析函数,由宿主插件按运行环境决定返回什么实例- ASP.NET 场景下,
AddAspNet()插件让它返回当前 Web 请求作用域的IMessageBus - 消费消息时,它返回当前正在处理的消息作用域的实例
- 底层基于
AsyncLocal绑定到任务(Task),因此async/await跨线程时作用域也不会丢失
2.2 作用域意味着什么?
事件发布会加入正在进行的作用域(Web 请求作用域或消息处理作用域)。也就是说,在请求里发布的事件,其处理器与你的业务代码共享同一个 DI Scope,可以直接注入仓储、日志等服务——这是"进程内领域事件"能保持事务一致性的关键。
三、聚合根实战:三行代码发布领域事件
3.1 定义领域事件(一个 POCO 即可)
领域事件就是最简单的 POCO,示例中src/Samples/Sample.DomainEvents.Domain/OrderSubmittedEvent.cs仅 7 行:
public record OrderSubmittedEvent(Order Order) { public DateTime Timestamp { get; } = DateTime.UtcNow; }3.2 聚合根内发布事件
打开示例聚合根src/Samples/Sample.DomainEvents.Domain/Order.cs,提交订单的逻辑只有两行核心代码(第 37-43 行):
public async Task Submit() { State = OrderState.Submitted; // 1. 变更领域状态 var e = new OrderSubmittedEvent(this); await MessageBus.Current.Publish(e); // 2. 发布领域事件 }注意Submit()是async方法——Memory 传输默认阻塞式发布(事件处理器执行完才返回),这样await order.Submit()返回时,所有领域事件处理器已经执行完毕,业务语义最清晰。
3.3 事件处理器:实现 IConsumer
处理器实现IConsumer<T>接口即可被发现,示例见src/Samples/Sample.DomainEvents.Application/DomainEventHandlers/OrderSubmittedHandler.cs:
public class OrderSubmittedHandler : IConsumer<OrderSubmittedEvent> { public Task OnHandle(OrderSubmittedEvent e, CancellationToken ct) { // 记录审计日志、生成物流单…… } }处理器从 DI 容器按作用域解析,可以随意注入ILogger等依赖。
四、最快配置方法:Memory 传输 + ASP.NET 插件
整个 Web 项目只需一处注册,见src/Samples/Sample.DomainEvents.WebApi/Startup.cs第 40-46 行:
services.AddSlimMessageBus(mbb => { mbb.WithProviderMemory() // 进程内传输 .AutoDeclareFromAssemblyContaining<OrderSubmittedHandler>() // 自动扫描声明消费者 .AddAspNet(); // 让 MessageBus.Current 感知 Web 请求作用域 }); services.AddHttpContextAccessor(); // AspNet 插件的必需依赖三个配置点各管一件事:
| 配置 | 作用 |
|---|---|
WithProviderMemory() | 使用进程内传输,零外部依赖 |
AutoDeclareFromAssemblyContaining<T>() | 反射扫描程序集,自动声明所有IConsumer<T>消费者 |
AddAspNet() | 让MessageBus.Current绑定到当前 Web 请求作用域 |
💡 需要跨进程、持久化或高吞吐时,把
WithProviderMemory()换成 RabbitMQ、Kafka、Azure Service Bus 等官方传输插件即可,业务代码一行不改。
五、完整调用链:从 HTTP 请求到事件分发
以src/Samples/Sample.DomainEvents.WebApi/Controllers/OrdersController.cs的下单接口为例,整条链路非常直观:
POST /api/orders → new Order(customer) 创建聚合根 → order.Add(...) 添加订单行 → await order.Submit() 聚合根发布 OrderSubmittedEvent → MessageBus.Current.Publish(...) (Web 请求作用域内) → OrderSubmittedHandler.OnHandle(...) 执行审计/日志 → return Ok() 此时事件处理器已全部执行完毕SlimMessageBus 的管道还支持在任意环节插入拦截器,例如审计日志、重试策略、FluentValidation 校验等,整体管道示意如下:
六、新手避坑清单 ✅
- 忘记
AddHttpContextAccessor()——AddAspNet()依赖它注入 HTTP 上下文,漏掉后MessageBus.Current无法解析请求作用域 - 在请求作用域之外调用
MessageBus.Current—— 例如在static工具方法或线程池回调中调用会抛MessageBusException;确无作用域的场景请显式从 DI 注入IMessageBus - 事件发布时机—— Memory 传输默认阻塞式发布,若处理器执行缓慢且想改为异步,请参考 docs/provider_memory.md 中的 "Asynchronous Publish" 一节
- 处理器数量无上限—— 同一事件类型可以有任意多个
IConsumer<T>,SlimMessageBus 会自动广播给全部订阅者
小结
用 SlimMessageBus 实现 DDD 领域事件的精髓可以浓缩为一句话:聚合根await MessageBus.Current.Publish(event),处理器实现IConsumer<T>,其余交给AutoDeclareFromAssemblyContaining。借助MessageBus.Current静态访问器的作用域感知能力,领域事件与 Web 请求共享同一个 DI Scope,既解耦又保持了事务一致性——而这,正是进程内消息总线最优雅的用法。
【免费下载链接】SlimMessageBusLightweight message bus interface for .NET (pub/sub and request-response) with transport plugins for popular message brokers.项目地址: https://gitcode.com/gh_mirrors/sl/SlimMessageBus
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考