Использую MediatR, .net 7, EF.
Есть пользователь, у которого на данный момент есть 2 события: AccountPasswordChangedEvent, TokensWasRemovedEvent.
AccountPasswordChangedEvent вызывается в UpdateUserPasswordCommandHandler:
private readonly IAccountService accountService;
private readonly IDomainEventsService eventsService;
public UpdateUserPasswordCommandHandler(IAccountService accountService, IDomainEventsService eventsService)
{
this.accountService = accountService;
this.eventsService = eventsService;
}
public async Task<ServiceResult<ResetPasswordResponse>> Handle(UpdateUserPasswordCommand request, CancellationToken cancellationToken)
{
var (userId, res) = await accountService.ResetPasswordAsync(request, false, cancellationToken);
if (res.Succeeded)
{
await eventsService.PublishAsync(new AccountPasswordChanged(userId));
}
return new ServiceResult<ResetPasswordResponse>(res);
}
В свою очередь обработчик AccountPasswordChangedEvent удаляет все токены пользователя:
private readonly IAccountService accountService;
public AccountPasswordChangedHandler(IAccountService accountService)
{
this.accountService = accountService;
}
public Task Handle(DomainEventNotification<AccountPasswordChangedEvent> notification, CancellationToken cancellationToken)
{
return accountService.RemoveTokenAsync(new LogoutCommand
{
UserId = notification.DomainEvent.UserId,
RemoveType = RemoveTokenType.All
}, cancellationToken);
}
TokensWasRemovedEvent вызывается в LogoutCommandHandler:
private readonly IAccountService accountService;
private readonly IDomainEventsService publisher;
public LogoutCommandHandler(IAccountService accountService, IDomainEventsService publisher)
{
this.accountService = accountService;
this.publisher = publisher;
}
public async Task<ServiceResult<LogoutResponse>> Handle(LogoutCommand request, CancellationToken cancellationToken)
{
var result = await accountService.RemoveTokenAsync(request, cancellationToken);
if (result.Length > 0)
{
await publisher.PublishAsync(new TokensWasRemovedEvent(result));
}
return new ServiceResult<LogoutResponse>(new LogoutResponse { Count = result.Length });
}
Где обработчик этого события удаляет все удаленные токены из кеша:
private readonly IMemoryCache memoryCache;
public TokenWasRemovedHandler(IMemoryCache memoryCache)
{
this.memoryCache = memoryCache;
}
public Task Handle(DomainEventNotification<TokensWasRemovedEvent> notification, CancellationToken cancellationToken)
{
foreach (var item in notification.DomainEvent.Tokens)
{
memoryCache.Remove($"apitoken={item}");
}
return Task.CompletedTask;
}
Вопрос такой: можно ли пробросить новое событие TokensWasRemovedEvent в обработчике события AccountPasswordChangedHandler? Правильно ли это будет (могут ли события порождать события? Я такого не видел нигде, а если быть точнее, то видел и там было сказано, что это плохо)?
private readonly IAccountService accountService;
private readonly IDomainEventsService publisher;
public AccountPasswordChangedHandler(IAccountService accountService, IDomainEventsService publisher)
{
this.accountService = accountService;
this.publisher = publisher;
}
public async Task Handle(DomainEventNotification<AccountPasswordChangedEvent> notification, CancellationToken cancellationToken)
{
var tokens = await accountService.RemoveTokenAsync(new LogoutCommand
{
UserId = notification.DomainEvent.UserId,
RemoveType = RemoveTokenType.All
}, cancellationToken);
if (tokens.Length > 0)
{
await publisher.PublishAsync(new TokensWasRemovedEvent(tokens));
}
}
Сразу отвечаю на вопрос "Почему события добавляются не через DbContext & Entity?" - удаление токенов происходит через ExecuteDeleteAsync (.net 7), который не затрагивает SaveChangesAsync. Смена пароля происходит через UserManager из которого нужно сначала получить ответ, что пароль изменен успешно (может вернуть IdentityError[]).