Skip to content

Domain Event Resolver

El servicio DomainEventResolverService se encarga de resolver tipos de eventos de dominio basados en nombres de eventos y atributos asociados. Este servicio simplifica la identificación de tipos de eventos, garantizando que los eventos sean manejados de manera eficiente y consistente en la aplicación.

namespace CodeDesignPlus.Net.Core.Abstractions;
/// <summary>
/// Represents a service for resolving domain event types and keys.
/// </summary>
public interface IDomainEventResolver
{
/// <summary>
/// Gets the type of the domain event based on the event name.
/// </summary>
/// <param name="eventName">The name of the event.</param>
/// <returns>The type of the domain event.</returns>
Type GetDomainEventType(string eventName);
/// <summary>
/// Gets the type of the domain event based on the generic type parameter.
/// </summary>
/// <typeparam name="TDomainEvent">The type of the domain event.</typeparam>
/// <returns>The type of the domain event.</returns>
Type GetDomainEventType<TDomainEvent>() where TDomainEvent : IDomainEvent;
/// <summary>
/// Gets the key of the domain event based on the generic type parameter.
/// </summary>
/// <typeparam name="TDomainEvent">The type of the domain event.</typeparam>
/// <returns>The key of the domain event.</returns>
string GetKeyDomainEvent<TDomainEvent>() where TDomainEvent : IDomainEvent;
/// <summary>
/// Gets the key of the domain event based on the type parameter.
/// </summary>
/// <param name="type">The type of the domain event.</param>
/// <returns>The key of the domain event.</returns>
string GetKeyDomainEvent(Type type);
}

Métodos


El servicio DomainEventResolverService proporciona una serie de métodos para resolver y gestionar eventos de dominio. A continuación, se detallan los métodos principales y su funcionalidad:

GetDomainEventType

Type: public Type GetDomainEventType(string eventName)

Obtiene el tipo de evento de dominio basado en el nombre del evento. Si el nombre no se encuentra en el diccionario interno, lanza una excepción. Es útil para traducir nombres de eventos en sus tipos correspondientes para su manejo.

GetDomainEventType

Type: public Type GetDomainEventType<TDomainEvent>() where TDomainEvent : IDomainEvent;

Devuelve el tipo de evento de dominio basado en un parámetro genérico. Utiliza el método GetKeyEvent<TDomainEvent>() para resolver la clave asociada al tipo.

GetKeyDomainEvent

Type: public string GetKeyDomainEvent<TDomainEvent>() where TDomainEvent : IDomainEvent;

Obtiene la clave del evento de dominio basado en un tipo genérico. Esto permite a los desarrolladores identificar la clave asociada a un evento particular para propósitos de registro o análisis.

GetKeyDomainEvent

Type: public string GetKeyDomainEvent(Type type);

Obtiene la clave del evento de dominio basado en un tipo específico. Este método es una alternativa cuando el tipo no está disponible como un parámetro genérico.

¿Cómo Funciona?

Cuando se publica un evento en estos sistemas, el entorno de ejecución en C# necesita saber qué clase utilizar para serializar el evento en formato JSON. Aquí es donde el Domain Event Resolver entra en acción. Este servicio se encarga de escanear todos los eventos de dominio que tienen el atributo EventKeyAttribute, almacenando en un diccionario la relación entre el nombre del evento y su tipo correspondiente.

Esto permite que otras librerías del SDK, como CodeDesignPlus.Net.Redis.PubSub, CodeDesignPlus.Net.Redis.Kafka o CodeDesignPlus.Net.Redis.RabbitMQ, utilicen este servicio para identificar la clase correcta que deben instanciar y con la que deben serializar los eventos.

Atributo EventKeyAttribute


El atributo EventKeyAttribute es fundamental para la correcta identificación y gestión de los eventos de dominio en el sistema. Este atributo proporciona metadatos sobre el evento, incluyendo la entidad asociada, la versión y el nombre del evento. A continuación, se detalla su estructura y uso.

namespace CodeDesignPlus.Net.Core.Abstractions.Attributes;
/// <summary>
/// Specifies the key information for an event.
/// </summary>
/// <remarks>
/// This attribute can be applied to any element.
/// </remarks>
/// <remarks>
/// Initializes a new instance of the <see cref="EventKeyAttribute"/> class with the specified entity, version, and event.
/// </remarks>
/// <param name="entity">The entity associated with the event.</param>
/// <param name="version">The version of the event.</param>
/// <param name="event">The name of the event.</param>
/// <param name="appName">The name of the application that generates the event or the name of the application to listen to the event.</param>
[AttributeUsage(AttributeTargets.All)]
public class EventKeyAttribute(string entity, ushort version, string @event, string? appName = null) : Attribute
{
/// <summary>
/// Gets the version of the event.
/// </summary>
public string Version { get; } = $"v{version}";
/// <summary>
/// Gets the entity associated with the event.
/// </summary>
public string Entity { get; } = entity;
/// <summary>
/// Gets the name of the event.
/// </summary>
public string Event { get; } = @event;
/// <summary>
/// Gets the name of the application that generates the event or the name of the application to listen to the event.
/// </summary>
public string? AppName { get; } = appName;
}
/// <summary>
/// Specifies the key information for an event.
/// </summary>
/// <typeparam name="TAggregate">The entity associated with the event.</typeparam>
/// <param name="version">The version of the event.</param>
/// <param name="event">The name of the event.</param>
/// <param name="appName">The name of the application that generates the event or the name of the application to listen to the event.</param>
[AttributeUsage(AttributeTargets.Class)]
public class EventKeyAttribute<TAggregate>(ushort version, string @event, string? appName = null)
: EventKeyAttribute(typeof(TAggregate).Name, version, @event, appName) where TAggregate : IEntityBase;

Este atributo puede ser aplicado a cualquier clase o elemento que represente un evento de dominio. Permite que el servicio DomainEventResolverService pueda escanear y registrar eventos de manera eficiente, asegurando que la serialización y deserialización de eventos sean coherentes y fáciles de manejar.

Ejemplo de Uso

A continuación, se presenta un ejemplo de cómo aplicar el EventKeyAttribute a un evento de dominio:

using CodeDesignPlus.Net.Core.Abstractions;
using CodeDesignPlus.Net.Core.Abstractions.Attributes;
using CodeDesignPlus.Net.Core.Sample.Resources.Aggregate;
namespace CodeDesignPlus.Net.Core.Sample.Resources.DomainEvents;
[EventKey<OrderAggregate>(1, "created")]
public class OrderCreatedDomainEvent(
Guid aggregateId,
string name,
string description,
decimal price,
Instant createdAt,
Guid createBy,
Instant? updatedAt,
Guid? eventId = null,
Instant? occurredAt = null,
Dictionary<string, object>? metadata = null
) : DomainEvent(aggregateId, eventId, occurredAt, metadata)
{
public string Name { get; private set; } = name;
public string Description { get; private set; } = description;
public decimal Price { get; private set; } = price;
public Instant CreatedAt { get; private set; } = createdAt;
public Guid CreateBy { get; private set; } = createBy;
public Instant? UpdatedAt { get; private set; } = updatedAt;
}

Implementación


El servicio DomainEventResolverService es la clase que implementa IDomainEventResolver. Es parte fundamental de la arquitectura de eventos de dominio en CodeDesignPlus.Net.Core. A continuación, se muestra su implementación detallada:

namespace CodeDesignPlus.Net.Core.Services;
/// <summary>
/// Service responsible for resolving domain event types based on event names and attributes.
/// </summary>
public class DomainEventResolverService : IDomainEventResolver
{
/// <summary>
/// Dictionary that maps event names to their corresponding types.
/// </summary>
private readonly Dictionary<string, Type> eventTypes = [];
/// <summary>
/// The options for the core functionality of the application.
/// </summary>
private readonly CoreOptions coreOptions;
/// <summary>
/// Initializes a new instance of the <see cref="DomainEventResolverService"/> class.
/// </summary>
/// <param name="options">The options for the core functionality of the application.</param>
public DomainEventResolverService(IOptions<CoreOptions> options)
{
ArgumentNullException.ThrowIfNull(options);
this.coreOptions = options.Value;
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(x => typeof(DomainEvent).IsAssignableFrom(x) && !x.IsAbstract);
foreach (var type in types)
{
var attribute = type.GetCustomAttribute<EventKeyAttribute>();
if (attribute is not null)
{
var key = $"{coreOptions.Business}.{coreOptions.AppName}.{attribute.Version}.{attribute.Entity}.{attribute.Event}".ToLower();
eventTypes.Add(key, type);
}
}
}
/// <summary>
/// Gets the type of the domain event based on the event name.
/// </summary>
/// <param name="eventName">The name of the event.</param>
/// <returns>The type of the domain event.</returns>
public Type GetDomainEventType(string eventName)
{
ArgumentNullException.ThrowIfNull(eventName);
if (!eventTypes.TryGetValue(eventName, out var type))
throw new CoreException($"The event type {eventName} does not exist");
return type;
}
/// <summary>
/// Gets the type of the domain event based on the generic type parameter.
/// </summary>
/// <typeparam name="TDomainEvent">The type of the domain event.</typeparam>
/// <returns>The type of the domain event.</returns>
public Type GetDomainEventType<TDomainEvent>() where TDomainEvent : IDomainEvent
{
return GetDomainEventType(GetKeyDomainEvent<TDomainEvent>());
}
/// <summary>
/// Gets the key of the domain event based on the generic type parameter.
/// </summary>
/// <typeparam name="TDomainEvent">The type of the domain event.</typeparam>
/// <returns>The key of the domain event.</returns>
public string GetKeyDomainEvent<TDomainEvent>() where TDomainEvent : IDomainEvent
{
return this.GetKeyDomainEvent(typeof(TDomainEvent));
}
/// <summary>
/// Gets the key of the domain event based on the type.
/// </summary>
/// <param name="type">The type of the domain event.</param>
/// <exception cref="CoreException">The event does not have the KeyAttribute.</exception>
/// <returns>The key of the domain event.</returns>
public string GetKeyDomainEvent(Type type)
{
ArgumentNullException.ThrowIfNull(type);
var attribute = type.GetCustomAttribute<EventKeyAttribute>();
CoreException.ThrowIfNull(attribute, type.Name);
return $"{coreOptions.Business}.{attribute.AppName ?? coreOptions.AppName}.{attribute.Version}.{attribute.Entity}.{attribute.Event}".ToLower();
}
}

El estándar de nomenclatura del evento sigue una convención estructurada que combina varios elementos para garantizar una identificación única y coherente de los eventos de dominio. Este formato es "{coreOptions.Business}.{coreOptions.AppName}.{attribute.Version}.{attribute.Entity}.{attribute.Event}".ToLower();, donde cada componente tiene un propósito específico:

  • coreOptions.Business: representa el contexto de negocio al que pertenece el evento.
  • coreOptions.AppName: indica la aplicación que genera el evento.
  • attribute.Version: especifica la versión del evento para gestionar cambios en el tiempo.
  • attribute.Entity: se refiere a la entidad involucrada en el evento.
  • attribute.Event: denota el tipo de evento en sí.

Al concatenar estos elementos y convertir el resultado a minúsculas, se asegura que los nombres de los eventos sean consistentes y fácilmente legibles, lo que facilita la suscripción y publicación en sistemas de mensajería.

Ejemplo de Uso

Imaginemos que estamos desarrollando un sistema de comercio electrónico y queremos manejar eventos de creación de pedidos. Cuando un pedido es creado, se publica un evento OrderCreatedDomainEvent.

  1. Definir el Evento de Dominio Primero, definimos nuestro evento de dominio utilizando el atributo EventKeyAttribute.

    using CodeDesignPlus.Net.Core.Abstractions;
    using CodeDesignPlus.Net.Core.Abstractions.Attributes;
    using CodeDesignPlus.Net.Core.Sample.Resources.Aggregate;
    namespace CodeDesignPlus.Net.Core.Sample.Resources.DomainEvents;
    [EventKey<OrderAggregate>(1, "created")]
    public class OrderCreatedDomainEvent(
    Guid aggregateId,
    string name,
    string description,
    decimal price,
    Instant createdAt,
    Guid createBy,
    Instant? updatedAt,
    Guid? eventId = null,
    Instant? occurredAt = null,
    Dictionary<string, object>? metadata = null
    ) : DomainEvent(aggregateId, eventId, occurredAt, metadata)
    {
    public string Name { get; private set; } = name;
    public string Description { get; private set; } = description;
    public decimal Price { get; private set; } = price;
    public Instant CreatedAt { get; private set; } = createdAt;
    public Guid CreateBy { get; private set; } = createBy;
    public Instant? UpdatedAt { get; private set; } = updatedAt;
    }
  2. Publicar el Evento Cuando un nuevo pedido es creado, publicamos el evento:

    using CodeDesignPlus.Net.Core.Abstractions;
    using CodeDesignPlus.Net.Core.Abstractions.Attributes;
    using CodeDesignPlus.Net.Core.Services;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    // Servicio de manejo de eventos
    public class EventPublisher
    {
    private readonly IDomainEventResolver _domainEventResolverService;
    public EventPublisher(IDomainEventResolver domainEventResolverService)
    {
    _domainEventResolverService = domainEventResolverService;
    }
    public void Publish<TDomainEvent>(TDomainEvent domainEvent) where TDomainEvent : IDomainEvent
    {
    var eventName = _domainEventResolverService.GetKeyDomainEvent(domainEvent.GetType());
    // Aquí se podría agregar lógica para publicar el evento a un sistema de mensajería como RabbitMQ o Kafka
    Console.WriteLine($"Event Published: {eventName}");
    }
    }
    // Escenario de uso
    var configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .Build();
    var serviceCollection = new ServiceCollection();
    serviceCollection.AddSingleton(configuration);
    serviceCollection.AddCore(configuration);
    var serviceProvider = serviceCollection.BuildServiceProvider();
    var domainEventResolverService = serviceProvider.GetRequiredService<IDomainEventResolver>();
    var eventPublisher = new EventPublisher(domainEventResolverService);
    // Crear y publicar un evento
    var orderCreatedEvent = new OrderCreatedDomainEvent(Guid.NewGuid());
    eventPublisher.Publish(orderCreatedEvent);
    // Obtener el tipo del evento usando el resolver
    var eventType = domainEventResolverService.GetDomainEventType("businessName.appName.v1.order.created");
    Console.WriteLine($"Event Type Resolved: {eventType.FullName}");

Conlusión


El Domain Event Resolver simplifica la gestión de eventos en arquitecturas basadas en microservicios al proporcionar una forma coherente y eficiente de resolver y serializar eventos de dominio. Al escanear automáticamente los eventos y almacenar sus relaciones, permite que las aplicaciones se centren en la lógica de negocio, en lugar de lidiar con las complejidades de la serialización y la instanciación de clases.