Eventos de dominio: diseño e implementaciónDomain events: design and implementation

Uso de eventos de dominio para implementar explícitamente los efectos secundarios de los cambios en el dominio.Use domain events to explicitly implement side effects of changes within your domain. En otras palabras y con la terminología de DDD, usar eventos de dominio para implementar explícitamente los efectos secundarios entre varios agregados.In other words, and using DDD terminology, use domain events to explicitly implement side effects across multiple aggregates. Opcionalmente, para una mejor escalabilidad y un menor impacto en los bloqueos de base de datos, use la coherencia final entre agregados dentro del mismo dominio.Optionally, for better scalability and less impact in database locks, use eventual consistency between aggregates within the same domain.

¿Qué es un evento de dominio?What is a domain event?

Un evento es algo que ha sucedido en el pasado.An event is something that has happened in the past. Un evento de dominio es algo que ha sucedido en el dominio que quiere que otras partes del mismo dominio (en curso) tengan en cuenta.A domain event is, something that happened in the domain that you want other parts of the same domain (in-process) to be aware of. Normalmente las partes notificadas reaccionan de alguna manera a los eventos.The notified parts usually react somehow to the events.

Una ventaja importante de los eventos de dominio es que los efectos secundarios se pueden expresar explícitamente.An important benefit of domain events is that side effects can be expressed explicitly.

Por ejemplo, si simplemente usa Entity Framework y debe haber una reacción a algún evento, probablemente codificaría cualquier cosa que necesite cerca de lo que desencadena el evento.For example, if you're just using Entity Framework and there has to be a reaction to some event, you would probably code whatever you need close to what triggers the event. Por tanto, la regla se acopla, implícitamente, en el código, y tendrá que mirar el código para, con suerte, descubrir que la regla se implementa allí.So the rule gets coupled, implicitly, to the code, and you have to look into the code to, hopefully, realize the rule is implemented there.

Por otro lado, el uso de los eventos de dominio hace el concepto explícito, porque hay un DomainEvent y al menos un DomainEventHandler implicados.On the other hand, using domain events makes the concept explicit, because there is a DomainEvent and at least one DomainEventHandler involved.

Por ejemplo, en la aplicación eShopOnContainers, cuando se crea un pedido, el usuario se convierte en un comprador, por tanto se genera un OrderStartedDomainEvent y se controla en el ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler, por lo que el concepto subyacente es evidente.For example, in the eShopOnContainers application, when an order is created, the user becomes a buyer, so an OrderStartedDomainEvent is raised and handled in the ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler, so the underlying concept is evident.

En resumen, los eventos de dominio le ayudan a expresar explícitamente las reglas de dominio, en función del lenguaje ubicuo proporcionado por los expertos de dominio.In short, domain events help you to express, explicitly, the domain rules, based in the ubiquitous language provided by the domain experts. Además, los eventos de dominio permiten una mejor separación de cuestiones entre clases dentro del mismo dominio.Domain events also enable a better separation of concerns among classes within the same domain.

Es importante asegurarse de que, al igual que en una transacción de base de datos, o todas las operaciones relacionadas con un evento de dominio finalizan correctamente o ninguna lo hace.It's important to ensure that, just like a database transaction, either all the operations related to a domain event finish successfully or none of them do.

Los eventos de dominio son similares a los eventos de estilo de mensajería, con una diferencia importante.Domain events are similar to messaging-style events, with one important difference. Con la mensajería real, las colas de mensajes, los agentes de mensajes o un bus de servicio con AMQP, un mensaje siempre se envía de forma asincrónica y se comunica entre procesos y equipos.With real messaging, message queuing, message brokers, or a service bus using AMQP, a message is always sent asynchronously and communicated across processes and machines. Esto es útil para integrar varios contextos delimitados, microservicios o incluso otras aplicaciones.This is useful for integrating multiple Bounded Contexts, microservices, or even different applications. Pero con los eventos de dominio, le interesa generar un evento desde la operación de dominio que se está ejecutando actualmente, pero que los efectos secundarios se produzcan dentro del mismo dominio.However, with domain events, you want to raise an event from the domain operation you are currently running, but you want any side effects to occur within the same domain.

Los eventos de dominio y sus efectos secundarios (las acciones iniciadas después que se administren mediante controladores de eventos) se deben producir casi de inmediato, por lo general en el proceso y dentro del mismo dominio.The domain events and their side effects (the actions triggered afterwards that are managed by event handlers) should occur almost immediately, usually in-process, and within the same domain. Por tanto, los eventos de dominio pueden ser sincrónicos o asincrónicos.Thus, domain events could be synchronous or asynchronous. Pero los eventos de integración siempre deben ser asincrónicos.Integration events, however, should always be asynchronous.

Eventos de dominio frente a eventos de integraciónDomain events versus integration events

Semánticamente, los eventos de dominio y los de integración son lo mismo: notificaciones sobre algo que acaba de ocurrir.Semantically, domain and integration events are the same thing: notifications about something that just happened. Pero su implementación debe ser diferente.However, their implementation must be different. Los eventos de dominio son simplemente mensajes insertados en un distribuidor de eventos de dominio, que se puede implementar como un mediador en memoria basado en un contenedor de IoC o cualquier otro método.Domain events are just messages pushed to a domain event dispatcher, which could be implemented as an in-memory mediator based on an IoC container or any other method.

Por otro lado, el propósito de los eventos de integración es propagar las transacciones confirmadas y actualizaciones a subsistemas adicionales, con independencia de que sean otros microservicios, contextos delimitados o incluso aplicaciones externas.On the other hand, the purpose of integration events is to propagate committed transactions and updates to additional subsystems, whether they are other microservices, Bounded Contexts or even external applications. Por tanto, solo se deben producir si la entidad se conserva correctamente, de otra forma será como si toda la operación nunca se hubiera producido.Hence, they should occur only if the entity is successfully persisted, otherwise it's as if the entire operation never happened.

Como se ha mencionado anteriormente, los eventos de integración se deben basar en la comunicación asincrónica entre varios microservicios (otros contextos delimitados) o incluso sistemas o aplicaciones externos.As mentioned before, integration events must be based on asynchronous communication between multiple microservices (other Bounded Contexts) or even external systems/applications.

Por tanto, la interfaz de bus de eventos necesita una infraestructura que permita la comunicación entre procesos y distribuida entre servicios potencialmente remotos.Thus, the event bus interface needs some infrastructure that allows inter-process and distributed communication between potentially remote services. Se pueden basar en un bus de servicio comercial, colas, una base de datos compartida que se use como un buzón o cualquier otro sistema de mensajería distribuido e, idealmente, basado en inserciones.It can be based on a commercial service bus, queues, a shared database used as a mailbox, or any other distributed and ideally push based messaging system.

Eventos de dominio como método preferido para desencadenar efectos secundarios entre varios agregados dentro del mismo dominioDomain events as a preferred way to trigger side effects across multiple aggregates within the same domain

Si la ejecución de un comando relacionado con una instancia de agregado requiere reglas de dominio adicionales para ejecutarse en uno o varios agregados adicionales, debe diseñar e implementar esos efectos secundarios para que se desencadenen mediante eventos de dominio.If executing a command related to one aggregate instance requires additional domain rules to be run on one or more additional aggregates, you should design and implement those side effects to be triggered by domain events. Como se muestra en la figura 7-14 y como uno de los casos de uso más importantes, se debe usar un evento de dominio para propagar los cambios de estado entre varios agregados dentro del mismo modelo de dominio.As shown in Figure 7-14, and as one of the most important use cases, a domain event should be used to propagate state changes across multiple aggregates within the same domain model.

Diagrama que muestra un evento de dominio que controla los datos a un agregado Buyer.

Figura 7-14.Figure 7-14. Eventos de dominio para exigir la coherencia entre varios agregados dentro del mismo dominioDomain events to enforce consistency between multiple aggregates within the same domain

En la figura 7-14 se muestra cómo consiguen los eventos de dominio la coherencia entre los agregados.Figure 7-14 shows how consistency between aggregates is achieved by domain events. Cuando el usuario inicia un pedido, el agregado Order envía un evento de dominio OrderStarted.When the user initiates an order, the Order Aggregate sends an OrderStarted domain event. El agregado Buyer controla el evento de dominio OrderStarted para crear un objeto Buyer en el microservicio de pedidos, según la información de usuario original del microservicio de identidades (con la información proporcionada en el comando CreateOrder).The OrderStarted domain event is handled by the Buyer Aggregate to create a Buyer object in the ordering microservice, based on the original user info from the identity microservice (with information provided in the CreateOrder command).

Como alternativa, puede hacer que la raíz agregada se suscriba a los eventos generados por los miembros de sus agregados (las entidades secundarias).Alternately, you can have the aggregate root subscribed for events raised by members of its aggregates (child entities). Por ejemplo, cada entidad secundaria OrderItem puede generar un evento cuando el precio del artículo sea mayor que una cantidad específica, o bien cuando la cantidad del elemento de producto sea demasiado alta.For instance, each OrderItem child entity can raise an event when the item price is higher than a specific amount, or when the product item amount is too high. Después, la raíz agregada puede recibir esos eventos y realizar un cálculo o una agregación global.The aggregate root can then receive those events and perform a global calculation or aggregation.

Es importante comprender que este tipo de comunicación basada en eventos no se implementa de forma directa dentro de los agregados; tendrá que implementar controladores de eventos de dominio.It is important to understand that this event-based communication is not implemented directly within the aggregates; you need to implement domain event handlers.

El control de los eventos de dominio es una cuestión de la aplicación.Handling the domain events is an application concern. El nivel de modelo de dominio solo debe centrarse en la lógica del dominio, en lo que un experto de dominio debería entender, no en la infraestructura de la aplicación como controladores y acciones de persistencia de efectos secundarios mediante repositorios.The domain model layer should only focus on the domain logic—things that a domain expert would understand, not application infrastructure like handlers and side-effect persistence actions using repositories. Por tanto, el nivel de aplicación es donde los controladores de eventos de dominio deberían desencadenar acciones cuando se produzca un evento de dominio.Therefore, the application layer level is where you should have domain event handlers triggering actions when a domain event is raised.

Los eventos de dominio también se pueden usar para desencadenar cualquier número de acciones de la aplicación y, lo que es más importante, deben ser abiertos para aumentar ese número en el futuro de forma desacoplada.Domain events can also be used to trigger any number of application actions, and what is more important, must be open to increase that number in the future in a decoupled way. Por ejemplo, al iniciar el pedido, es posible que le interese publicar un evento de dominio para propagar esa información a otros agregados o incluso para generar acciones de la aplicación como notificaciones.For instance, when the order is started, you might want to publish a domain event to propagate that info to other aggregates or even to raise application actions like notifications.

El punto clave es el número abierto de acciones que se van a ejecutar cuando se produce un evento de dominio.The key point is the open number of actions to be executed when a domain event occurs. Con el tiempo, las acciones y reglas en el dominio y la aplicación aumentarán.Eventually, the actions and rules in the domain and application will grow. La complejidad o el número de acciones de efectos secundarios aumentará cuando ocurra algo, pero si el código se acoplara con "adherencia" (es decir, la creación de objetos específicos con new), cada vez que necesitara agregar una acción nueva también tendría que cambiar el código funcional y probado.The complexity or number of side-effect actions when something happens will grow, but if your code were coupled with "glue" (that is, creating specific objects with new), then every time you needed to add a new action you would also need to change working and tested code.

Este cambio podría provocar nuevos errores y este enfoque también va en contra del principio abierto/cerrado de SOLID.This change could result in new bugs and this approach also goes against the Open/Closed principle from SOLID. No solo eso, la clase original que orquestaba las operaciones no dejaría de crecer, algo contrario al Principio de responsabilidad única (SRP).Not only that, the original class that was orchestrating the operations would grow and grow, which goes against the Single Responsibility Principle (SRP).

Por otro lado, si usa eventos de dominio, puede crear una implementación específica y desacoplada mediante la separación de las responsabilidades de esta manera:On the other hand, if you use domain events, you can create a fine-grained and decoupled implementation by segregating responsibilities using this approach:

  1. Envíe un comando (por ejemplo, CreateOrder).Send a command (for example, CreateOrder).
  2. Reciba el comando en un controlador de comandos.Receive the command in a command handler.
    • Ejecute la transacción de un solo agregado.Execute a single aggregate's transaction.
    • (Opcional) Genere eventos de dominio para los efectos secundarios (por ejemplo, OrderStartedDomainEvent).(Optional) Raise domain events for side effects (for example, OrderStartedDomainEvent).
  3. Controle los eventos de dominio (en el proceso actual) que van a ejecutar un número abierto de efectos secundarios en varios agregados o acciones de la aplicación.Handle domain events (within the current process) that will execute an open number of side effects in multiple aggregates or application actions. Por ejemplo:For example:
    • Compruebe o cree el comprador y el método de pago.Verify or create buyer and payment method.
    • Cree y envíe un evento de integración relacionado al bus de eventos para propagar los estados entre los microservicios o desencadenar acciones externas como enviar un correo electrónico al comprador.Create and send a related integration event to the event bus to propagate states across microservices or trigger external actions like sending an email to the buyer.
    • Controle otros efectos secundarios.Handle other side effects.

Como se muestra en la figura 7-15, empezando desde el mismo evento de dominio, puede controlar varias acciones relacionadas con otros agregados en el dominio o acciones de la aplicación adicionales que tenga que realizar entre los microservicios conectados con eventos de integración y el bus de eventos.As shown in Figure 7-15, starting from the same domain event, you can handle multiple actions related to other aggregates in the domain or additional application actions you need to perform across microservices connecting with integration events and the event bus.

Diagrama que muestra un evento de dominio que pasa datos a varios controladores de eventos.

Figura 7-15.Figure 7-15. Control de varias acciones por dominioHandling multiple actions per domain

Puede haber varios controladores para el mismo evento de dominio en el nivel de aplicación, un controlador puede resolver la coherencia entre agregados y otro controlador puede publicar un evento de integración, por lo que otros microservicios pueden hacer algo con él.There can be several handlers for the same domain event in the Application Layer, one handler can solve consistency between aggregates and another handler can publish an integration event, so other microservices can do something with it. Normalmente los controladores de eventos se encuentran en el nivel de aplicación, porque los objetos de infraestructura como los repositorios o una API de aplicación se usarán para el comportamiento del microservicio.The event handlers are typically in the application layer, because you will use infrastructure objects like repositories or an application API for the microservice's behavior. En ese sentido, los controladores de eventos son similares a los controladores de comandos, por lo que ambos forman parte del nivel de aplicación.In that sense, event handlers are similar to command handlers, so both are part of the application layer. La diferencia más importante es que un comando solo se debe procesar una vez.The important difference is that a command should be processed only once. Un evento de dominio se podría procesar cero o n veces, porque lo pueden recibir varios receptores o controladores de eventos con un propósito diferente para cada controlador.A domain event could be processed zero or n times, because it can be received by multiple receivers or event handlers with a different purpose for each handler.

Tener un número abierto de controladores de eventos de dominio permite agregar tantas reglas de dominio como sea necesario sin que el código actual se vea afectado.Having an open number of handlers per domain event allows you to add as many domain rules as needed, without affecting current code. Por ejemplo, la implementación de la siguiente regla de negocio podría ser tan fácil como agregar algunos controladores de eventos (o incluso solo uno):For instance, implementing the following business rule might be as easy as adding a few event handlers (or even just one):

Cuando la cantidad total adquirida por un cliente en el almacén, en cualquier número de pedidos, supera los 6000 dólares, se aplica un 10 % de descuento a cada pedido nuevo y se notifica ese descuento para futuros pedidos a los clientes con un correo electrónico.When the total amount purchased by a customer in the store, across any number of orders, exceeds $6,000, apply a 10% off discount to every new order and notify the customer with an email about that discount for future orders.

Implementación de eventos de dominioImplement domain events

En C#, un evento de dominio es simplemente una estructura o clase que almacena datos, como un DTO, con toda la información relacionada con lo que ha sucedido en el dominio, como se muestra en el ejemplo siguiente:In C#, a domain event is simply a data-holding structure or class, like a DTO, with all the information related to what just happened in the domain, as shown in the following example:

public class OrderStartedDomainEvent : INotification
{
    public string UserId { get; }
    public int CardTypeId { get; }
    public string CardNumber { get; }
    public string CardSecurityNumber { get; }
    public string CardHolderName { get; }
    public DateTime CardExpiration { get; }
    public Order Order { get; }

    public OrderStartedDomainEvent(Order order,
                                   int cardTypeId, string cardNumber,
                                   string cardSecurityNumber, string cardHolderName,
                                   DateTime cardExpiration)
    {
        Order = order;
        CardTypeId = cardTypeId;
        CardNumber = cardNumber;
        CardSecurityNumber = cardSecurityNumber;
        CardHolderName = cardHolderName;
        CardExpiration = cardExpiration;
    }
}

Esto es básicamente una clase que contiene todos los datos relacionados con el evento OrderStarted.This is essentially a class that holds all the data related to the OrderStarted event.

En cuanto al lenguaje ubicuo del dominio, como un evento es algo que tuvo lugar en el pasado, el nombre de clase del evento se debe representar como un verbo en pasado, como OrderStartedDomainEvent u OrderShippedDomainEvent.In terms of the ubiquitous language of the domain, since an event is something that happened in the past, the class name of the event should be represented as a past-tense verb, like OrderStartedDomainEvent or OrderShippedDomainEvent. Esta es la forma de implementar el evento de dominio en el microservicio de pedidos en eShopOnContainers.That's how the domain event is implemented in the ordering microservice in eShopOnContainers.

Como se indicó anteriormente, una característica importante de los eventos es que, como un evento es algo que se produjo en el pasado, no debe cambiar.As noted earlier, an important characteristic of events is that since an event is something that happened in the past, it should not change. Por tanto, debe ser una clase inmutable.Therefore, it must be an immutable class. En el código anterior se puede ver que las propiedades son de solo lectura.You can see in the previous code that the properties are read-only. No hay ninguna manera de actualizar el objeto, solo se pueden establecer valores al crearlo.There's no way to update the object, you can only set values when you create it.

Es importante destacar aquí que si los eventos de dominio tuvieran que administrarse de forma asincrónica, mediante una cola que necesitase serializar y deserializar los objetos de eventos, las propiedades tendrían que ser "conjunto privado" en lugar de solo lectura, por lo que el deserializador podría asignar los valores tras quitar de la cola.It's important to highlight here that if domain events were to be handled asynchronously, using a queue that required serializing and deserializing the event objects, the properties would have to be "private set" instead of read-only, so the deserializer would be able to assign the values upon dequeuing. Esto no es un problema en el microservicio Ordering, ya que el evento de dominio pub/sub se implementa sincrónicamente con MediatR.This is not an issue in the Ordering microservice, as the domain event pub/sub is implemented synchronously using MediatR.

Generación de eventos de dominioRaise domain events

La siguiente pregunta es cómo generar un evento de dominio para que llegue a sus controladores de eventos relacionados.The next question is how to raise a domain event so it reaches its related event handlers. Se pueden usar varios enfoques.You can use multiple approaches.

Originalmente, Udi Dahan propuso el uso de una clase estática para administrar y generar los eventos (por ejemplo, en algunas publicaciones relacionadas, como Domain Events – Take 2 [Eventos de dominio: Toma 2]).Udi Dahan originally proposed (for example, in several related posts, such as Domain Events – Take 2) using a static class for managing and raising the events. Esto podría incluir una clase estática denominada DomainEvents que generaría los eventos de dominio inmediatamente cuando se llama, con una sintaxis similar a DomainEvents.Raise(Event myEvent).This might include a static class named DomainEvents that would raise domain events immediately when it is called, using syntax like DomainEvents.Raise(Event myEvent). Jimmy Bogard escribió una entrada de blog [Strengthening your domain: Domain Events (Reforzar el dominio: eventos de dominio)] que recomienda un enfoque similar.Jimmy Bogard wrote a blog post (Strengthening your domain: Domain Events) that recommends a similar approach.

Pero cuando la clase de eventos de dominio es estática, también lo envía a los controladores inmediatamente.However, when the domain events class is static, it also dispatches to handlers immediately. Esto dificulta las pruebas y la depuración, dado que los controladores de eventos con la lógica de efectos secundarios se ejecutan inmediatamente después de que se genera el evento.This makes testing and debugging more difficult, because the event handlers with side-effects logic are executed immediately after the event is raised. Durante las pruebas y la depuración, únicamente le interesa centrarse en lo que sucede en las clases agregadas actuales; no quiere que de repente se le redirija a otros controladores de eventos para los efectos secundarios relacionados con otros agregados o la lógica de la aplicación.When you are testing and debugging, you just want to focus on what is happening in the current aggregate classes; you do not want to suddenly be redirected to other event handlers for side effects related to other aggregates or application logic. Es el motivo de que otros métodos hayan evolucionado, como se explica en la sección siguiente.This is why other approaches have evolved, as explained in the next section.

El enfoque diferido para generar y enviar eventosThe deferred approach to raise and dispatch events

En lugar de enviar a un controlador de eventos de dominio de forma inmediata, un método más adecuado consiste en agregar los eventos de dominio a una colección y, después, enviarlos justo antes o justo después de confirmar la transacción (como ocurre con SaveChanges en EF).Instead of dispatching to a domain event handler immediately, a better approach is to add the domain events to a collection and then to dispatch those domain events right before or right after committing the transaction (as with SaveChanges in EF). (Este enfoque lo describió Jimmy Bogard en esta publicación A better domain events pattern [Un patrón de eventos de dominio mejor]).(This approach was described by Jimmy Bogard in this post A better domain events pattern.)

Decidir si enviar los eventos de dominio justo antes o justo después de confirmar la transacción es importante, ya que determina si se van a incluir los efectos secundarios como parte de la misma transacción o en transacciones diferentes.Deciding if you send the domain events right before or right after committing the transaction is important, since it determines whether you will include the side effects as part of the same transaction or in different transactions. En este último caso, debe controlar la coherencia final entre varios agregados.In the latter case, you need to deal with eventual consistency across multiple aggregates. Este tema se analiza en la sección siguiente.This topic is discussed in the next section.

El enfoque diferido es el que se usa en eShopOnContainers.The deferred approach is what eShopOnContainers uses. En primer lugar, se agregan los eventos que tienen lugar en las entidades a una colección o lista de eventos por entidad.First, you add the events happening in your entities into a collection or list of events per entity. Esa lista debe formar parte del objeto de entidad, o incluso mejor, de la clase de entidad base, como se muestra en el ejemplo siguiente de la clase base Entity:That list should be part of the entity object, or even better, part of your base entity class, as shown in the following example of the Entity base class:

public abstract class Entity
{
     //...
     private List<INotification> _domainEvents;
     public List<INotification> DomainEvents => _domainEvents;

     public void AddDomainEvent(INotification eventItem)
     {
         _domainEvents = _domainEvents ?? new List<INotification>();
         _domainEvents.Add(eventItem);
     }

     public void RemoveDomainEvent(INotification eventItem)
     {
         _domainEvents?.Remove(eventItem);
     }
     //... Additional code
}

Cuando quiera generar un evento, simplemente agréguelo a la colección de eventos desde el código en cualquier método de la entidad raíz agregada.When you want to raise an event, you just add it to the event collection from code at any method of the aggregate-root entity.

En el código siguiente, parte de la raíz agregada Order de eShopOnContainers, se muestra un ejemplo:The following code, part of the Order aggregate-root at eShopOnContainers, shows an example:

var orderStartedDomainEvent = new OrderStartedDomainEvent(this, //Order object
                                                          cardTypeId, cardNumber,
                                                          cardSecurityNumber,
                                                          cardHolderName,
                                                          cardExpiration);
this.AddDomainEvent(orderStartedDomainEvent);

Tenga en cuenta que lo único que hace el método AddDomainEvent es agregar un evento a la lista.Notice that the only thing that the AddDomainEvent method is doing is adding an event to the list. Todavía no se distribuye ningún evento, ni tampoco se invoca ningún controlador de eventos.No event is dispatched yet, and no event handler is invoked yet.

Lo que realmente le interesa es enviar los eventos después, cuando la transacción se confirme en la base de datos.You actually want to dispatch the events later on, when you commit the transaction to the database. Si usa Entity Framework Core, eso significa hacerlo en el método SaveChanges del DbContext de EF, como en el código siguiente:If you are using Entity Framework Core, that means in the SaveChanges method of your EF DbContext, as in the following code:

// EF Core DbContext
public class OrderingContext : DbContext, IUnitOfWork
{
    // ...
    public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        // Dispatch Domain Events collection.
        // Choices:
        // A) Right BEFORE committing data (EF SaveChanges) into the DB. This makes
        // a single transaction including side effects from the domain event
        // handlers that are using the same DbContext with Scope lifetime
        // B) Right AFTER committing data (EF SaveChanges) into the DB. This makes
        // multiple transactions. You will need to handle eventual consistency and
        // compensatory actions in case of failures.
        await _mediator.DispatchDomainEventsAsync(this);

        // After this line runs, all the changes (from the Command Handler and Domain
        // event handlers) performed through the DbContext will be committed
        var result = await base.SaveChangesAsync();
    }
}

Con este código, los eventos de entidad se envían a sus controladores de eventos correspondientes.With this code, you dispatch the entity events to their respective event handlers.

El resultado general es que se separa la generación de un evento de dominio (una sencilla adición a una lista en memoria) de su envío a un controlador de eventos.The overall result is that you have decoupled the raising of a domain event (a simple add into a list in memory) from dispatching it to an event handler. Además, en función del tipo de distribuidor que se use, los eventos se podrían enviar de forma sincrónica o asincrónica.In addition, depending on what kind of dispatcher you are using, you could dispatch the events synchronously or asynchronously.

Tenga en cuenta que aquí los límites transaccionales tienen una importancia especial.Be aware that transactional boundaries come into significant play here. Si la unidad de trabajo y la transacción pueden abarcar más de un agregado (como ocurre cuando se usa EF Core y una base de datos relacional), esto puede funcionar bien.If your unit of work and transaction can span more than one aggregate (as when using EF Core and a relational database), this can work well. Pero si la transacción no puede abarcar agregados, como cuando se usa una base de datos NoSQL como Azure CosmosDB, se deben implementar pasos adicionales para lograr la coherencia.But if the transaction cannot span aggregates, such as when you are using a NoSQL database like Azure CosmosDB, you have to implement additional steps to achieve consistency. Este es otro motivo por el que la omisión de persistencia no es universal; depende del sistema de almacenamiento que se use.This is another reason why persistence ignorance is not universal; it depends on the storage system you use.

Transacción única entre agregados frente a coherencia final entre agregadosSingle transaction across aggregates versus eventual consistency across aggregates

La pregunta de si se debe realizar una única transacción entre agregados en lugar de depender de la coherencia final entre esos agregados es controvertida.The question of whether to perform a single transaction across aggregates versus relying on eventual consistency across those aggregates is a controversial one. Muchos autores de DDD, como Eric Evans y Vaughn Vernon, promueven la regla "una transacción = un agregado" y argumentan, por tanto, la coherencia final entre agregados.Many DDD authors like Eric Evans and Vaughn Vernon advocate the rule that one transaction = one aggregate and therefore argue for eventual consistency across aggregates. Por ejemplo, en su libro Domain-Driven Design (Diseño controlado por eventos), Eric Evans afirma lo siguiente:For example, in his book Domain-Driven Design, Eric Evans says this:

No se espera que las reglas que abarcan agregados estén actualizadas en todo momento.Any rule that spans Aggregates will not be expected to be up-to-date at all times. A través del procesamiento de eventos, el procesamiento por lotes u otros mecanismos de actualización, se pueden resolver otras dependencias dentro de un periodo determinado.Through event processing, batch processing, or other update mechanisms, other dependencies can be resolved within some specific time. (página 128)(page 128)

Vaughn Vernon afirma lo siguiente en Effective Aggregate Design. Part II: Making Aggregates Work Together (Diseño eficaz de agregados - Parte II: hacer que los agregados funcionen de forma conjunta):Vaughn Vernon says the following in Effective Aggregate Design. Part II: Making Aggregates Work Together:

Por tanto, si la ejecución de un comando en una instancia del agregado requiere que se ejecuten reglas de negocio adicionales en uno o varios agregados, use la coherencia final [...] Hay una forma práctica de admitir la coherencia final en un modelo de DDD.Thus, if executing a command on one aggregate instance requires that additional business rules execute on one or more aggregates, use eventual consistency [...] There is a practical way to support eventual consistency in a DDD model. Un método de agregado publica un evento de dominio que con el tiempo se entrega a uno o varios suscriptores asincrónicos.An aggregate method publishes a domain event that is in time delivered to one or more asynchronous subscribers.

Esta lógica se basa en la adopción de transacciones específicas en lugar de transacciones distribuidas entre varios agregados o entidades.This rationale is based on embracing fine-grained transactions instead of transactions spanning many aggregates or entities. La idea es que, en el segundo caso, el número de bloqueos de base de datos será relevante en aplicaciones a gran escala con necesidades de alta escalabilidad.The idea is that in the second case, the number of database locks will be substantial in large-scale applications with high scalability needs. Asumir el hecho de que las aplicaciones de alta escalabilidad no necesitan coherencia transaccional entre varios agregados ayudará a aceptar el concepto de la coherencia final.Embracing the fact that highly scalable applications need not have instant transactional consistency between multiple aggregates helps with accepting the concept of eventual consistency. A menudo los cambios atómicos no son necesarios por parte de la empresa y, en cualquier caso, es la responsabilidad de los expertos de dominio indicar si determinadas operaciones necesitan transacciones atómicas o no.Atomic changes are often not needed by the business, and it is in any case the responsibility of the domain experts to say whether particular operations need atomic transactions or not. Si una operación siempre necesita una transacción atómica entre varios agregados, podría preguntarse si el agregado debe ser mayor o no se ha diseñado correctamente.If an operation always needs an atomic transaction between multiple aggregates, you might ask whether your aggregate should be larger or was not correctly designed.

Pero otros desarrolladores y arquitectos como Jimmy Bogard se conforman con una sola transacción que abarque varios agregados, pero solo cuando esos agregados adicionales estén relacionados con efectos secundarios para el mismo comando original.However, other developers and architects like Jimmy Bogard are okay with spanning a single transaction across several aggregates—but only when those additional aggregates are related to side effects for the same original command. Por ejemplo, en A better domain events pattern, Bogard afirma lo siguiente:For instance, in A better domain events pattern, Bogard says this:

Normalmente, me interesa que los efectos secundarios de un evento de dominio se produzcan en la misma transacción lógica, pero no necesariamente en el mismo ámbito de generación del evento de dominio [...] Justo antes de que se confirme la transacción, los eventos se envían a sus correspondientes controladores.Typically, I want the side effects of a domain event to occur within the same logical transaction, but not necessarily in the same scope of raising the domain event [...] Just before we commit our transaction, we dispatch our events to their respective handlers.

Si los eventos de dominio se envían justo antes de confirmar la transacción original, es porque interesa que los efectos secundarios de esos eventos se incluyan en la misma transacción.If you dispatch the domain events right before committing the original transaction, it is because you want the side effects of those events to be included in the same transaction. Por ejemplo, si se produce un error en el método SaveChanges de DbContext de EF, la transacción revertirá todos los cambios, incluido el resultado de cualquier operación de efecto secundario implementada por los controladores de eventos de dominio relacionados.For example, if the EF DbContext SaveChanges method fails, the transaction will roll back all changes, including the result of any side effect operations implemented by the related domain event handlers. Esto se debe a que el ámbito de la duración de DbContext se define de forma predeterminada como "en ámbito".This is because the DbContext life scope is by default defined as "scoped." Por tanto, el objeto DbContext se comparte entre varios objetos de repositorio de los que se crean instancias en el mismo ámbito o gráfico de objetos.Therefore, the DbContext object is shared across multiple repository objects being instantiated within the same scope or object graph. Esto coincide con el ámbito de HttpRequest al desarrollar aplicaciones de API web o MVC.This coincides with the HttpRequest scope when developing Web API or MVC apps.

En realidad, ambos enfoques (única transacción atómica y coherencia final) pueden ser correctos.Actually, both approaches (single atomic transaction and eventual consistency) can be right. Realmente depende de los requisitos empresariales o de dominio, y de lo que los expertos de dominio digan.It really depends on your domain or business requirements and what the domain experts tell you. También depende de la capacidad de escalabilidad que deba tener el servicio (las transacciones más granulares tienen un impacto menor en relación con los bloqueos de base de datos).It also depends on how scalable you need the service to be (more granular transactions have less impact with regard to database locks). Y depende de la inversión que esté dispuesto a realizar en el código, puesto que la coherencia final requiere un código más complejo con el fin de detectar posibles incoherencias entre los agregados y la necesidad de implementar acciones de compensación.And it depends on how much investment you are willing to make in your code, since eventual consistency requires more complex code in order to detect possible inconsistencies across aggregates and the need to implement compensatory actions. Tenga en cuenta que si confirma los cambios en el agregado original y después, cuando los eventos se distribuyan, si se produce un problema y los controladores de eventos no pueden confirmar sus efectos secundarios, tendrá incoherencias entre los agregados.Consider that if you commit changes to the original aggregate and afterwards, when the events are being dispatched, if there is an issue and the event handlers cannot commit their side effects, you will have inconsistencies between aggregates.

Una manera de permitir acciones de compensación sería almacenar los eventos de dominio en tablas de base de datos adicionales para que puedan formar parte de la transacción original.A way to allow compensatory actions would be to store the domain events in additional database tables so they can be part of the original transaction. Después, podría tener un proceso por lotes que detectara las incoherencias y ejecutara acciones de compensación comparando la lista de eventos con el estado actual de los agregados.Afterwards, you could have a batch process that detects inconsistencies and runs compensatory actions by comparing the list of events with the current state of the aggregates. Las acciones de compensación forman parte de un tema complejo que requerirá un análisis profundo por su parte, incluido su análisis con los usuarios empresariales y expertos de dominio.The compensatory actions are part of a complex topic that will require deep analysis from your side, which includes discussing it with the business user and domain experts.

En cualquier caso, puede elegir el enfoque que necesite.In any case, you can choose the approach you need. Pero el enfoque diferido inicial (generar los eventos antes de la confirmación y usar una sola transacción) es el más sencillo cuando se usa EF Core y una base de datos relacional.But the initial deferred approach—raising the events before committing, so you use a single transaction—is the simplest approach when using EF Core and a relational database. Es más fácil de implementar y resulta válido en muchos casos empresariales.It is easier to implement and valid in many business cases. También es el enfoque que se usa en el microservicio de pedidos de eShopOnContainers.It is also the approach used in the ordering microservice in eShopOnContainers.

¿Pero cómo se envían realmente los eventos a sus correspondientes controladores de eventos?But how do you actually dispatch those events to their respective event handlers? ¿Qué es el objeto _mediator que ve en el ejemplo anterior?What's the _mediator object you see in the previous example? Tiene que ver con las técnicas y los artefactos que se usan para la asignación entre eventos y sus controladores de eventos.It has to do with the techniques and artifacts you use to map between events and their event handlers.

El distribuidor de eventos de dominio: asignación de eventos a controladores de eventosThe domain event dispatcher: mapping from events to event handlers

Una vez que se puedan enviar o publicar los eventos, se necesita algún tipo de artefacto que publique el evento, para que todos los controladores relacionados puedan obtenerlo y procesar efectos secundarios en función de ese evento.Once you're able to dispatch or publish the events, you need some kind of artifact that will publish the event, so that every related handler can get it and process side effects based on that event.

Un enfoque es un sistema de mensajería real o incluso un bus de eventos, posiblemente basado en un bus de servicio en lugar de en eventos en memoria.One approach is a real messaging system or even an event bus, possibly based on a service bus as opposed to in-memory events. Pero para el primer caso, la mensajería real sería excesiva para el procesamiento de eventos de dominio, ya que solo es necesario procesar los eventos dentro del mismo proceso (es decir, dentro del mismo nivel de dominio y aplicación).However, for the first case, real messaging would be overkill for processing domain events, since you just need to process those events within the same process (that is, within the same domain and application layer).

Otra manera de asignar eventos a varios controladores de eventos consiste en usar el registro de tipos en un contenedor de IoC para poder inferir de forma dinámica a dónde enviar los eventos.Another way to map events to multiple event handlers is by using types registration in an IoC container so you can dynamically infer where to dispatch the events. En otras palabras, debe saber qué controladores de eventos tienen que obtener un evento específico.In other words, you need to know what event handlers need to get a specific event. En la figura 7-16 se muestra un enfoque simplificado para esto.Figure 7-16 shows a simplified approach for this approach.

Diagrama que muestra un distribuidor de eventos de dominio que envía eventos a los controladores adecuados.

Figura 7-16.Figure 7-16. Distribuidor de eventos de dominio con IoCDomain event dispatcher using IoC

Puede crear usted mismo todos los artefactos para implementar este enfoque.You can build all the plumbing and artifacts to implement that approach by yourself. Pero también puede usar bibliotecas disponibles como MediatR, que interiormente usa el contenedor de IoC.However, you can also use available libraries like MediatR that uses your IoC container under the covers. Por tanto, puede usar directamente las interfaces predefinidas y los métodos de publicación y distribución del objeto de mediador.You can therefore directly use the predefined interfaces and the mediator object's publish/dispatch methods.

En el código, primero debe registrar los tipos de controlador de eventos en el contenedor de IoC, como se muestra en el ejemplo siguiente del microservicio de pedidos de eShopOnContainers:In code, you first need to register the event handler types in your IoC container, as shown in the following example at eShopOnContainers Ordering microservice:

public class MediatorModule : Autofac.Module
{
    protected override void Load(ContainerBuilder builder)
    {
        // Other registrations ...
        // Register the DomainEventHandler classes (they implement IAsyncNotificationHandler<>)
        // in assembly holding the Domain Events
        builder.RegisterAssemblyTypes(typeof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler)
                                       .GetTypeInfo().Assembly)
                                         .AsClosedTypesOf(typeof(IAsyncNotificationHandler<>));
        // Other registrations ...
    }
}

En primer lugar, el código identifica el ensamblado que contiene los controladores de eventos de dominio localizando el ensamblado que contiene cualquiera de los controladores (mediante typeof(ValidateOrAddBuyerAggregateWhenXxxx), pero podría haber elegido cualquier otro controlador de eventos para buscar el ensamblado).The code first identifies the assembly that contains the domain event handlers by locating the assembly that holds any of the handlers (using typeof(ValidateOrAddBuyerAggregateWhenXxxx), but you could have chosen any other event handler to locate the assembly). Como todos los controladores de eventos implementan la interfaz IAsyncNotificationHandler, el código solo busca esos tipos y registra todos los controladores de eventos.Since all the event handlers implement the IAsyncNotificationHandler interface, the code then just searches for those types and registers all the event handlers.

Cómo suscribirse a eventos de dominioHow to subscribe to domain events

Cuando se usa MediatR, todos los controladores de eventos deben usar un tipo de evento que se proporciona en el parámetro genérico de la interfaz INotificationHandler, como se puede ver en el código siguiente:When you use MediatR, each event handler must use an event type that is provided on the generic parameter of the INotificationHandler interface, as you can see in the following code:

public class ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler
  : IAsyncNotificationHandler<OrderStartedDomainEvent>

En función de la relación entre el evento y el controlador de eventos, que se puede considerar la suscripción, el artefacto de MediatR puede detectar todos los controladores de eventos para cada evento y desencadenar cada uno de ellos.Based on the relationship between event and event handler, which can be considered the subscription, the MediatR artifact can discover all the event handlers for each event and trigger each one of those event handlers.

Cómo controlar eventos de dominioHow to handle domain events

Por último, el controlador de eventos normalmente implementa código de nivel de aplicación en el que se usan repositorios de infraestructura para obtener los agregados adicionales necesarios y para ejecutar la lógica del dominio de efectos secundarios.Finally, the event handler usually implements application layer code that uses infrastructure repositories to obtain the required additional aggregates and to execute side-effect domain logic. En el siguiente código de controlador de eventos de dominio de eShopOnContainers, se muestra un ejemplo de implementación.The following domain event handler code at eShopOnContainers, shows an implementation example.

public class ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler
                   : INotificationHandler<OrderStartedDomainEvent>
{
    private readonly ILoggerFactory _logger;
    private readonly IBuyerRepository<Buyer> _buyerRepository;
    private readonly IIdentityService _identityService;

    public ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler(
        ILoggerFactory logger,
        IBuyerRepository<Buyer> buyerRepository,
        IIdentityService identityService)
    {
        // ...Parameter validations...
    }

    public async Task Handle(OrderStartedDomainEvent orderStartedEvent)
    {
        var cardTypeId = (orderStartedEvent.CardTypeId != 0) ? orderStartedEvent.CardTypeId : 1;
        var userGuid = _identityService.GetUserIdentity();
        var buyer = await _buyerRepository.FindAsync(userGuid);
        bool buyerOriginallyExisted = (buyer == null) ? false : true;

        if (!buyerOriginallyExisted)
        {
            buyer = new Buyer(userGuid);
        }

        buyer.VerifyOrAddPaymentMethod(cardTypeId,
                                       $"Payment Method on {DateTime.UtcNow}",
                                       orderStartedEvent.CardNumber,
                                       orderStartedEvent.CardSecurityNumber,
                                       orderStartedEvent.CardHolderName,
                                       orderStartedEvent.CardExpiration,
                                       orderStartedEvent.Order.Id);

        var buyerUpdated = buyerOriginallyExisted ? _buyerRepository.Update(buyer)
                                                                      : _buyerRepository.Add(buyer);

        await _buyerRepository.UnitOfWork
                .SaveEntitiesAsync();

        // Logging code using buyerUpdated info, etc.
    }
}

El código anterior de controlador de eventos de dominio se considera código de nivel de aplicación porque usa repositorios de infraestructura, como se explica en la sección siguiente sobre el nivel de persistencia de infraestructura.The previous domain event handler code is considered application layer code because it uses infrastructure repositories, as explained in the next section on the infrastructure-persistence layer. Los controladores de eventos también pueden usar otros componentes de infraestructura.Event handlers could also use other infrastructure components.

Los eventos de dominio pueden generar eventos de integración para publicarse fuera de los límites del microservicioDomain events can generate integration events to be published outside of the microservice boundaries

Por último, es importante mencionar que en ocasiones es posible que le interese propagar los eventos a través de varios microservicios.Finally, it's important to mention that you might sometimes want to propagate events across multiple microservices. Dicha propagación es un evento de integración y se podría publicar a través de un bus de eventos desde cualquier controlador de eventos de dominio específico.That propagation is an integration event, and it could be published through an event bus from any specific domain event handler.

Conclusiones sobre los eventos de dominioConclusions on domain events

Como se mencionó, los eventos de dominio se usan para implementar explícitamente los efectos secundarios de los cambios en el dominio.As stated, use domain events to explicitly implement side effects of changes within your domain. Para usar la terminología de DDD, los eventos de dominio se usan para implementar explícitamente los efectos secundarios a través de uno o varios agregados.To use DDD terminology, use domain events to explicitly implement side effects across one or multiple aggregates. Además, para una mejor escalabilidad y un menor impacto en los bloqueos de base de datos, la coherencia final se usa entre agregados dentro del mismo dominio.Additionally, and for better scalability and less impact on database locks, use eventual consistency between aggregates within the same domain.

La aplicación de referencia usa MediatR para propagar los eventos de dominio sincrónicamente entre agregados, dentro de una única transacción.The reference app uses MediatR to propagate domain events synchronously across aggregates, within a single transaction. No obstante, también puede usar una implementación de AMQP como RabbitMQ o Azure Service Bus para propagar los eventos de dominio de forma asincrónica con la coherencia eventual. Pero, como se mencionó anteriormente, hay que tener en cuenta la necesidad de acciones compensatorias en caso de que se produzcan errores.However, you could also use some AMQP implementation like RabbitMQ or Azure Service Bus to propagate domain events asynchronously, using eventual consistency but, as mentioned above, you have to consider the need for compensatory actions in case of failures.

Recursos adicionalesAdditional resources