领域(近似理解为实现某个功能的多个模型)事件可以切断领域模型之间的强依赖关系,事件发布后,由事件的处理者决定如何响应事件,以便于实现事件发布和事件处理的解耦。
MediatR
可实现进程内事件的传递,支持一对一和一对多,使用步骤如下:
NuGet安装MediatR.Extensions.Microsoft.DependencyInjection
在Program.cs中调用AddMediatR
方法进行注册,参数为事件处理者所在的程序集
builder.Services.AddMediatR(Assembly.Load("用MediatR实现领域事件"));
INotification
接口 public record TestEvent(string UserName) : INotification;
NotificationHandler
接口,泛型参数代表的是要处理的消息类型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
用来发布一对一事件。[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)
{
//不要写成Send
//如果使用await那么要等所有的Handle方法执行完才能继续执行
await mediator.Publish(new TestEvent(req.UserName));
return Ok("ok");
}
}
我们一般在操作EF Core的changeName、构造方法等方法中调用IMediator的publish方法来发布领域事件,但是在这些方法中立即处理发布的事件会有以下问题:
**解决方法:**把领域事件的发布延迟到上下文修改时,即在实体类中仅仅是注册领域事件,而在上下文中的SaveChanges方法中发布事件。
实现步骤:
public interface IDomainEvents
{
IEnumerable<INotification> GetDomainEvents();//获得注册的领域事件
void AddDomainEvent(INotification eventItem);//注册领域事件
void AddDomainEventIfAbsent(INotification eventItem);//如果领域事件不存在,则注册事件
void ClearDomainEvents();//清除领域事件
}
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;
}
}
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)
{ //ChangeTracker是上下文用来对实体类变化进行追踪的对象
//Entries获得所有实现了IDomainEvents接口的实体类
//只要包含了领域事件的所有实体
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());
//发布所有的领域事件,要放到代用父类的SaveChangeAsync之前
//因为这样事件的处理代码会在上下文模型修改保存之前执行
foreach (var domainEvent in domainEvents)
{
await mediator.Publish(domainEvent);
}
//调用父类方法
return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
}
public record UserUpdatedEvent(Guid Id):INotification;
public record UserAddedEvent(User Item):INotification;
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()
{
//提供无参构造方法。避免EF Core加载数据的时候调用有参的构造方法触发领域事件
}
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
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.SingleAsync(u=>u.Id== notification.Id);
var user = await context.Users.FindAsync(notification.Id);
logger.LogInformation($"通知用户{user.Email}的信息被修改");
}
}
[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();
}