9.2领域事件
基本使用
领域(近似理解为实现某个功能的多个模型)事件可以切断领域模型之间的强依赖关系,事件发布后,由事件的处理者决定如何响应事件,以便于实现事件发布和事件处理的解耦。
MediatR
可实现进程内事件的传递,支持一对一和一对多,使用步骤如下:
NuGet安装MediatR.Extensions.Microsoft.DependencyInjection
在Program.cs中调用AddMediatR
方法进行注册,参数为事件处理者所在的程序集
builder.Services.AddMediatR(Assembly.Load("用MediatR实现领域事件"));
- 定义在发布者和处理者之间进行数据传递的类,即消息类型。该类需要实现
INotification
接口
public record TestEvent(string UserName) : INotification;
- 事件处理者要实现
NotificationHandler<TNotification>
接口,泛型参数代表的是要处理的消息类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class TestEventHandler1 : INotificationHandler<TestEvent> { public Task Handle(TestEvent notification, CancellationToken cancellationToken) { Console.WriteLine($"我收到了{notification.UserName}"); return Task.CompletedTask; } } public class TestEventHandler2 : INotificationHandler<TestEvent> { public async Task Handle(TestEvent notification, CancellationToken cancellationToken) { await File.WriteAllTextAsync("d:/1.txt", $"来了{notification.UserName}"); } }
|
- 在需要发布事件的类中注入
IMediator
类型的服务,调用Publish
方法来发布一对多事件,Send
用来发布一对一事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| [Route("api/[controller]/[action]")] [ApiController] public class TestController : ControllerBase { private readonly IMediator mediator;
public TestController(IMediator mediator) { this.mediator = mediator; }
[HttpPost] public async Task<IActionResult> Login(LoginRequest req) { await mediator.Publish(new TestEvent(req.UserName)); return Ok("ok"); } }
|
EF Core中发布领域事件
我们一般在操作EF Core的changeName、构造方法等方法中调用IMediator的publish方法来发布领域事件,但是在这些方法中立即处理发布的事件会有以下问题:
- 可能存在重复发送领域事件的情况。如分别调用changeName,changeAge方法进行修改,由于每个changeXXX都会发布“实体类被修改”的事件,到导致出多次处理事件,其实只需最后执行一次就可以。
- 领域事件发布的太早。为了能够发布“新增实体类”的领域事件,我们一般在实体类的构造方法中发布领域事件,但可能存在数据验证没有通过等等的原因最终没有将新增实体类保存在数据库,那就会出现了事件发布过早的错误问题。
解决方法:把领域事件的发布延迟到上下文修改时,即在实体类中仅仅是注册领域事件,而在上下文中的SaveChanges方法中发布事件。
实现步骤:
- 为了方便实体类关于领域事件的管理,定义接口
1 2 3 4 5 6 7
| public interface IDomainEvents { IEnumerable<INotification> GetDomainEvents(); void AddDomainEvent(INotification eventItem); void AddDomainEventIfAbsent(INotification eventItem); void ClearDomainEvents(); }
|
- 为了简化实体类的编写,定义实体类的抽象类,该抽象类要实现自定义的IDomainEvents接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public abstract class BaseEntity : IDomainEvents { private List<INotification> DomainEvents = new();
public void AddDomainEvent(INotification eventItem) { DomainEvents.Add(eventItem); }
public void AddDomainEventIfAbsent(INotification eventItem) { if (!DomainEvents.Contains(eventItem)) { DomainEvents.Add(eventItem); } }
public void ClearDomainEvents() { DomainEvents.Clear(); }
public IEnumerable<INotification> GetDomainEvents() { return DomainEvents; } }
|
- 需要在上下文中保存数据的时候发布注册的领域事件,为了简化上下文代码的编写,声明上下文抽象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public abstract class BaseDbContext : DbContext { private IMediator mediator;
public BaseDbContext(DbContextOptions options, IMediator mediator) : base(options) { this.mediator = mediator; } public override int SaveChanges(bool acceptAllChangesOnSuccess) { throw new NotImplementedException("Don not call SaveChanges, please call SaveChangesAsync instead."); } public async override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) { var domainEntities = this.ChangeTracker.Entries<IDomainEvents>() .Where(x => x.Entity.GetDomainEvents().Any()); var domainEvents = domainEntities .SelectMany(x => x.Entity.GetDomainEvents()).ToList(); domainEntities.ToList() .ForEach(entity => entity.Entity.ClearDomainEvents()); foreach (var domainEvent in domainEvents) { await mediator.Publish(domainEvent); } return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); } }
|
- 编写传递领域事件的类
1 2
| public record UserUpdatedEvent(Guid Id):INotification; public record UserAddedEvent(User Item):INotification;
|
- 编写实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public class User: BaseEntity { public Guid Id { get; init; } public string UserName { get; init; } public string Email { get; private set; } public string? NickName { get; private set; } public int? Age { get; private set; } public bool IsDeleted { get; private set; } private User() { } public User(string userName,string email) { this.Id = Guid.NewGuid(); this.UserName = userName; this.Email = email; this.IsDeleted = false; AddDomainEvent(new UserAddedEvent(this)); } public void ChangeEmail(string value) { this.Email = value; AddDomainEventIfAbsent(new UserUpdatedEvent(Id)); } public void ChangeNickName(string? value) { this.NickName = value; AddDomainEventIfAbsent(new UserUpdatedEvent(Id)); } public void ChangeAge(int value) { this.Age = value; AddDomainEventIfAbsent(new UserUpdatedEvent(Id)); } }
|
- 实体上下文类继承自上面的
BaseDbContext
- 编写事件处理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public class NewUserSendEmailHandler : INotificationHandler<UserAddedEvent> { private readonly ILogger<NewUserSendEmailHandler> logger;
public NewUserSendEmailHandler(ILogger<NewUserSendEmailHandler> logger) { this.logger = logger; }
public Task Handle(UserAddedEvent notification, CancellationToken cancellationToken) { var user = notification.Item; logger.LogInformation($"向{user.Email}发送欢迎邮件"); return Task.CompletedTask; } }
public class ModifyUserLogHandler : INotificationHandler<UserUpdatedEvent> { private readonly UserDbContext context; private readonly ILogger<ModifyUserLogHandler> logger;
public ModifyUserLogHandler(UserDbContext context, ILogger<ModifyUserLogHandler> logger) { this.context = context; this.logger = logger; }
public async Task Handle(UserUpdatedEvent notification, CancellationToken cancellationToken) { var user = await context.Users.FindAsync(notification.Id); logger.LogInformation($"通知用户{user.Email}的信息被修改"); } }
|
- 在控制器中使用user的增删改查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| [HttpPut] [Route("{id}")] public async Task<IActionResult> Update(Guid id,UpdateUserRequest req) { User? user = context.Users.Find(id); if (user==null) { return NotFound($"id={id}的User不存在"); } user.ChangeAge(req.Age); user.ChangeEmail(req.Email); user.ChangeNickName(req.NickName); await context.SaveChangesAsync(); return Ok(); }
|