Aethel.Infrastructure.Hermes
2.0.0
dotnet add package Aethel.Infrastructure.Hermes --version 2.0.0
NuGet\Install-Package Aethel.Infrastructure.Hermes -Version 2.0.0
<PackageReference Include="Aethel.Infrastructure.Hermes" Version="2.0.0" />
paket add Aethel.Infrastructure.Hermes --version 2.0.0
#r "nuget: Aethel.Infrastructure.Hermes, 2.0.0"
// Install Aethel.Infrastructure.Hermes as a Cake Addin #addin nuget:?package=Aethel.Infrastructure.Hermes&version=2.0.0 // Install Aethel.Infrastructure.Hermes as a Cake Tool #tool nuget:?package=Aethel.Infrastructure.Hermes&version=2.0.0
Hermes
Version inicial
Arquitectura para despachador de eventos de dominio para las librerias de Aethel.Extensions.Domain. Esta arquitectura y proceso permite capturar los eventos de dominio generados por los agregados que implementan IAggregateRoot de la libreria de dominio de Aethel.Extensions a traves de la implementacion dentro de los repositorios de agregados. Este proceso permite implementar el sistema de bandeja de salida transaccional, pues define 3 artefactos para llevar a cabo este proceso.
Features
- Centralizacion de eventos de dominio inyectanto el servicio EventCollector en los repositorios
- Recopilacion de las notificaciones de dominio disponibles para convertir el evento de dominio
- Definicion de interfaces para almacenar en bandeja de entrada y salida, la implementacion es libre
- Definicion de proceso para despacho de eventos y almacenamiento
EventCollector
Hermes define una serie de artefactos que nos permiten generar eventos de dominio en nuestros agregados y recolectarlos justo en el repositorio, antes o despues de que se guarden en el sistema de almacenamiento. Supongamos que nuestra entidad que implementa AggregateRoot genera eventos y los almacena en la lista interna definida por la libreria de Aethel.Extensions.Domain. Cuando definamos nuestros repositorios para cada agregado, inyectamos el colector de eventos por constructor de la siguiente manera:
public class UserRepository : IUserRepository{
private readonly IEventCollector _eventCollector;
public UserRepository(IEventCollector eventCollector){
_eventCollector = eventCollector;
}
}
Al hacer esto, estamos inyectando una instancia con alcance de solicitud, lo que permite recollectar todos los eventos de dominio que se esten generando en esa misma solicitud, y esta instancia al estar compartida nos asegura eso, ademas que al estar aislada por el mismo alcance, es facil saber los limites de trabajo de la misma. Para recuperar los eventos que los agregados generan debemos de hacer una llamada al collector de eventos en cada operacion de escritura que el repositorio realice.
public class UserRepository : IUserRepository{
// Some code here
public void Save(User user){
// Operaciones para guardar la entidad
_eventCollector.ExtractEvents(user);
}
// Some code here
}
De esta forma extraemos los eventos de los agregados y limpiamos los agregados para evitar la repeticion de eventos de dominio. Asi logramos que el colector de eventos no sea dependiente de marcos de almacenamiento como EF y nos permite tener esa facilidad para extraer los eventos desde la fuente misma de ellos.
EventResolver
El resolver para los eventos esta disponible, pero solo nos sirve para encontrar el tipo de origen y el tipo de destino al cual debe de convertirse un evento de dominio. Si tenemos el siguiente evento de dominio definido en una clase como:
public class UserCreatedEvent : IDomainEvent {
// Some code
}
Este evento solo puede ser consumido en el ambito de la solicitud que lo generó, pero, para implementar correctamente el patron de bandeja de entrada transaccional, convertimos este evento en una notificacion de dominio, que es el mismo evento de dominio envuelto en una clase de notificacion de dominio.
public class UserCreatedNotification : DomainNotification<UserCreatedEvent> {
// Some code
}
El resolver nos ayuda a encontrar todas las notificaciones de dominio disponibles en el ensamblado y a partir de esto, genera un diccionario en donde la clave es el nombre del evento que envuelve y el valor es la clase que implementa la notificacion de dominio.- Tanto los eventos de dominio como las notificaciones de dominio heredan de INotification de MediatR, lo que permite que ambos eventos puedan ser distribuidos a traves del bus de mediatr como notificaciones nativas.
EventDispatcher
El despachador de eventos se encarga de despachar los eventos, primero en el bus de eventos en el ambito actual, esto para ejecutar logica agregada en el mismo ambito de solicitud, agregando reactividad inmediata a los cambios en el sistema, y despues, transformando estos eventos de dominio en notificaciones de dominio a traves del eventResolver, permitiendo, a traves de IOutboxStorage, que se almacenen para su posterior procesamiento. Solo basta realizar una llamada al metodo await _dispatcher.DispatchEventsAsync() para que el proceso se realice automaticamente, y con esto, tenemos notificaciones para ser procesadas en un alcance nuevo a traves de procesos que permitan publicar estos eventos al recuperarlos del almacen de eventos de salida.
OutboxProcessing
Como parte esencial para completar este proceso, puede agregarse un comando que defina lo necesario para recuperar las notificaciones de dominio desde el almacen de eventos de salida y publicarlos dentro de un nuevo alcance para tener esta consistencia eventual. Proponemos el siguiente ejemplo que puede ayudarle a tomar desiciones de diseño con respecto a esto.
internal class OutboxProcessingHandler : ICommandHandler<OutboxProcessingCommand, Unit>
{
private readonly IMediator _mediator;
private readonly IOutboxStorage _storage;
private readonly IEventResolver _eventResolver;
public OutboxProcessingHandler(IMediator mediator, IOutboxStorage outboxStorage, IEventResolver eventResolver)
{
_mediator = mediator;
_storage = outboxStorage;
_eventResolver = eventResolver;
}
/// <summary>
/// Administra la ejecucion de publicacion de los eventos de tipo notificacion de dominio
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<Unit> Handle(OutboxProcessingCommand request, CancellationToken cancellationToken)
{
// Obtenemos los mensajes no publicados
var messages = await _storage.GetUnpublishedMessagesAsync();
// Si no hay eventos salimmos
if (!messages.Any())
return new Unit();
// Recorremos los eventos
foreach (var message in messages)
{
// Obtenemos el typo con el resolver
var type = _eventResolver.GetEventType(message.Type);
// Convertios los datos
var @event = JsonConvert.DeserializeObject(message.Content, type) as IDomainNotification;
// Si el evento es nulo, continuamos
if (@event is null)
continue;
// publicamos el evento
await _mediator.Publish(@event);
// Actualizamos el registro
message.ProcessedOn = DateTime.UtcNow;
// Actualizamos el registro
await _storage.UpdateAsync(message);
}
return new Unit();
}
}
Este comando definido dentro de los commandos de cqrs con MediatR, puede llamarse a traves de un proceso en segundo plano con el Programador que mas le interese implementar, solo basta una llamada como la siguiente para ejecutar el proceso anterior:
[DisallowConcurrentExecution]
public class OutboxProcessingJob : IJob
{
private readonly IIdentityManagementHost _identityManagementHost;
private readonly ILogger<OutboxProcessingJob> _logger;
public OutboxProcessingJob(IIdentityManagementHost identityManagement, ILogger<OutboxProcessingJob> logger)
{
_identityManagementHost = identityManagement;
_logger = logger;
}
/// <summary>
/// Ejecuta todo lo necessario para processar los mensajes en la bandeja de salida
/// dentro del modulo
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task Execute(IJobExecutionContext context)
{
_logger.LogInformation("Started outbox processing job");
await _identityManagementHost.ExecuteCommandAsync(new OutboxProcessingCommand());
_logger.LogInformation("Finalize outbox procesing job");
}
}
De esta forma completa el curso del evento hasta su publicacion final en un ambito nuevo, pero todo esto aun dentro del servicio, ddandole la felxibilidad necesaria para obtener todos los beneficios de CQRS y los eventos de dominio definidos por Domain Driven Design y tener codigo mas limpio
Versiones y fechas de lanzamiento
Version | Fecha de lanzamiento | Features | Status |
---|---|---|---|
1.0.0 | Mar 2020 | Toda la funcionalidad necesaria para implementar TransactionalOutbox con almacen de notificaciones de dominio con libre implementacion y el proceso desde la recollecion de eventos hasta el almacenados de los mismos en el almacen de bandeja de salida | Liberada |
License
MIT
If it works, learn how it do
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net6.0
- Aethel.Extensions.Domain (>= 2.0.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 6.0.0)
- Newtonsoft.Json (>= 13.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Se cambia el enfoque del paquete, solo distribuira y almacenara los eventos de dominio. Se olvidan los modelos de inbox y outbox al considerarse no utiles