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
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Aethel.Infrastructure.Hermes" Version="2.0.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Aethel.Infrastructure.Hermes --version 2.0.0
#r "nuget: Aethel.Infrastructure.Hermes, 2.0.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
2.0.0 886 4/9/2022
1.0.0 904 3/13/2022

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