本地事件总线
本地事件总线允许服务发布和订阅进程内事件。这意味着当两个服务(发布者和订阅者)运行在同一进程内时,此机制非常适合。
发布事件
以下章节将介绍两种发布本地事件的方法。
使用ILocalEventBus发布事件
可通过依赖注入获取ILocalEventBus实例,并用于发布本地事件。
示例:当商品库存数量变化时发布本地事件
using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Local;
namespace AbpDemo
{
public class MyService : ITransientDependency
{
private readonly ILocalEventBus _localEventBus;
public MyService(ILocalEventBus localEventBus)
{
_localEventBus = localEventBus;
}
public virtual async Task ChangeStockCountAsync(Guid productId, int newCount)
{
//TODO: 实现业务逻辑...
//发布事件
await _localEventBus.PublishAsync(
new StockCountChangedEvent
{
ProductId = productId,
NewCount = newCount
}
);
}
}
}
PublishAsync方法接收单个参数:事件对象,该对象负责保存与事件相关的数据。它是一个简单的普通类:
using System;
namespace AbpDemo
{
public class StockCountChangedEvent
{
public Guid ProductId { get; set; }
public int NewCount { get; set; }
}
}
即使不需要传输任何数据,也需要创建一个类(此时为一个空类)。
在实体/聚合根类内部发布事件
实体无法通过依赖注入获取服务,但在实体/聚合根类内部发布本地事件是非常常见的需求。
示例:在聚合根方法中发布本地事件
using System;
using Volo.Abp.Domain.Entities;
namespace AbpDemo
{
public class Product : AggregateRoot<Guid>
{
public string Name { get; set; }
public int StockCount { get; private set; }
private Product() { }
public Product(Guid id, string name)
: base(id)
{
Name = name;
}
public void ChangeStockCount(int newCount)
{
StockCount = newCount;
//添加待发布的事件
AddLocalEvent(
new StockCountChangedEvent
{
ProductId = Id,
NewCount = newCount
}
);
}
}
}
AggregateRoot类定义了AddLocalEvent方法用于添加新的本地事件,该事件将在聚合根对象保存到数据库时(创建、更新或删除操作后)被发布。
提示:如果实体发布此类事件,最佳实践是以受控方式修改相关属性,正如上例所示——
StockCount只能通过ChangeStockCount方法修改,这确保了事件的发布。
IGeneratesDomainEvents接口
实际上,添加本地事件并非AggregateRoot类独有。任何实体类均可实现IGeneratesDomainEvents接口。但AggregateRoot默认实现了该接口,为您提供了便利。
不建议为非聚合根的实体实现此接口,因为某些数据库提供程序可能不支持此类实体的该功能。例如,EF Core支持,但MongoDB不支持。
实现机制
调用AddLocalEvent不会立即发布事件。事件将在保存数据库变更时发布:
- 对于EF Core,在
DbContext.SaveChanges时发布。 - 对于MongoDB,在调用存储库的
InsertAsync、UpdateAsync或DeleteAsync方法时发布(因为MongoDB没有变更追踪系统)。
订阅事件
服务可通过实现ILocalEventHandler<TEvent>接口来处理事件。
示例:处理上述定义的StockCountChangedEvent
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus;
namespace AbpDemo
{
public class MyHandler
: ILocalEventHandler<StockCountChangedEvent>,
ITransientDependency
{
public async Task HandleEventAsync(StockCountChangedEvent eventData)
{
//TODO: 编写事件触发时执行的业务代码
}
}
}
至此完成。ABP会自动发现MyHandler,每当StockCountChangedEvent发生时就会调用HandleEventAsync方法。您可以在处理程序中注入任何服务并执行所需逻辑。
- 一个或多个处理程序可以订阅同一事件。
- 单个事件处理程序类可通过为每种事件类型实现
ILocalEventHandler<TEvent>接口来订阅多个事件。
如果在事件处理程序中执行数据库操作并使用存储库,可能需要创建一个工作单元,因为某些存储库方法需要在活动的工作单元中运行。将处理方法标记为virtual并为方法添加[UnitOfWork]特性,或手动使用IUnitOfWorkManager创建工作单元范围。
处理程序类必须注册到依赖注入(DI)容器。上例使用
ITransientDependency实现此目的。更多选项请参阅DI文档。
LocalEventHandlerOrder特性
LocalEventHandlerOrder特性可用于设置事件处理程序的执行顺序,这在需要按特定顺序处理事件时非常有用。
[LocalEventHandlerOrder(-1)]
public class MyHandler
: ILocalEventHandler<StockCountChangedEvent>,
ITransientDependency
{
public async Task HandleEventAsync(StockCountChangedEvent eventData)
{
//TODO: 编写事件触发时执行的业务代码
}
}
默认情况下,所有事件处理程序的顺序值为0。因此,如果希望某些事件处理程序先于其他处理程序执行,可将顺序值设置为负数。
LocalEventHandlerOrderAttribute属性
Order(int): 用于设置特定事件处理程序的执行顺序。
事务与异常行为
事件处理程序始终在与发布事件的代码相同的工作单元范围内执行,这意味着它们处于同一数据库事务中。如果事件处理程序抛出异常,工作单元(数据库事务)将回滚。因此,如果希望隐藏错误,请在事件处理程序中自行使用try-catch。
调用ILocalEventBus.PublishAsync时,事件处理程序不会立即执行。相反,它们会在当前工作单元完成前执行(处理程序中的未处理异常仍会导致当前工作单元回滚)。如果需要立即执行处理程序,可将可选的onUnitOfWorkComplete参数设为false。
除非有特殊需求,否则建议保持默认行为。在实体/聚合根类内部发布事件时(参见在实体/聚合根类内部发布事件章节),
onUnitOfWorkComplete选项不可用。
预构建事件
在实体创建、更新和删除操作时发布事件是非常常见的需求。ABP自动为所有实体发布这些事件。您只需订阅相关事件即可。
示例:订阅用户创建时发布的事件
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EventBus;
namespace AbpDemo
{
public class MyHandler
: ILocalEventHandler<EntityCreatedEventData<IdentityUser>>,
ITransientDependency
{
public async Task HandleEventAsync(
EntityCreatedEventData<IdentityUser> eventData)
{
var userName = eventData.Entity.UserName;
var email = eventData.Entity.Email;
//...
}
}
}
此类订阅了EntityCreatedEventData<IdentityUser>事件,该事件在用户创建后(但在当前事务完成前)发布。例如,您可能希望向新用户发送"欢迎"邮件。
预构建事件类型包括:
EntityCreatedEventData<T>:在实体成功创建后发布。EntityUpdatedEventData<T>:在实体成功更新后发布。EntityDeletedEventData<T>:在实体成功删除后发布。EntityChangedEventData<T>:在实体成功创建、更新或删除后发布。如果需要监听任何类型的变更(而不是单独订阅每个事件),这是一个快捷方式。
实现机制
预构建事件在保存数据库变更时发布:
- 对于EF Core,在
DbContext.SaveChanges时发布。 - 对于MongoDB,在调用存储库的
InsertAsync、UpdateAsync或DeleteAsync方法时发布(因为MongoDB没有变更追踪系统)。
AbpEntityChangeOptions
AbpEntityChangeOptions类中有一个PublishEntityUpdatedEventWhenNavigationChanges选项,默认值为true。
如果将其设置为false,当导航属性变更时不会发布EntityUpdatedEventData<T>。
此选项仅用于EF Core。
抠丁客


