Introduction

Some time ago I wrote post about publishing and handling domain events. In addition, in one of the posts I described the Outbox Pattern, which provides us At-Least-Once delivery when integrating with external components / services without using the 2PC protocol.

This time I wanted to present a combination of both approaches to complete previous posts. I will present a complete solution that enables reliable data processing in the system in a structured manner taking into account the transaction boundary.

Depth of the system

At the beginning I would like to describe what is a Shallow System and what is a Deep System.

Shallow System

The system is considered to be shallow when, most often, after doing some action on it, there is not much going on.

A typical and most popular example of this type of system is that it has many CRUD operations. Most of the operations involve managing the data shown on the screen and there is little business logic underneath. Sometimes such systems can also be called a database browser. 😉

Another heuristic that can point to a Shallow System is the ability to specify the requirements for such a system practically through the GUI prototype. The prototype of the system (possibly with the addition of comments) shows us how this system should work and it is enough – if nothing underneath is happening then there is nothing to define and describe.

From the Domain-Driven Design point of view, it will most often look like this: the execution of the Command on the Aggregate publishes exactly one Domain Event and… nothing happens. No subscribers to this event, no processors / handlers, no workflows defined. There is no communication with other contexts or 3rd systems either. It looks like this:

Execute action, process request, save data – end of story. Often, in such cases, we do not even need DDD. Transaction Script or Active Record will be enough.

Deep system

The Deep System is (as one could easily guess) the complete opposite of the Shallow System.

A Deep System is one that is designed to resolve some problems in a non-trivial and complicated domain. If the domain is complicated then the Domain Model will be complicated as well. Of course, the Domain Model should be simplified as it is possible and at the same time it should not lose aspects that are most important in a given context (in terms of DDD – Bounded Context). Nevertheless, it contains a lot of business logic that needs to be handled.

We do not specify a Deep System by the GUI prototype because too much is happening underneath. Saving or reading data is just one of the actions that our system does. Other activities are communication with other systems, complicated data processing or calling other parts of our system.

This time, much more is happening in the context of Domain-Driven Design implementation. Aggregates can publish multiple Domain Events , and for each Domain Event there can be many handlers responsible for different behavior. This behavior can be communication with an external system or executing a Command on another Aggregate, which will again publish its events to which another part of our system will subscribe. This scheme repeats itself and our Domain Model reacts in a reactive manner:

Problem

In post about publishing and handling domain events was presented very simple case and the whole solution did not support the re-publishing (and handling) of events by another Aggregate, which processing resulted from the previous Domain Event. In other words, there was no support for complex flows and data processing in a reactive way. Only one Command -> Aggregate -> Domain Event -> handlers scenario was possible.

It will be best to consider this in a specific example. Let’s assume the requirements that after placing an Order by the Customer:

a) Confirmation email to the Customer about placed Order should be sent

b) New Payment should be created

c) Email about new Payment to the Customer should be sent

These requirements are illustrated in the following picture:

Let’s assume that in this particular case both Order placement and Payment creation should take place in the same transaction. If transaction is successful, we need to send 2 emails – about the Order and Payment. Let’s see how we can implement this type of scenario.

Solution

The most important thing we have to keep in mind is the boundary of transaction. To make our life easier, we must make the following assumptions:

1. Command Handler defines transaction boundary. Transaction is started when Command Handler is invoked and committed at the end.

2. Each Domain Event handler is invoked in context of the same transaction boundary.

3. If we want to process something outside the transaction , we need to create a public event based on the Domain Event. I call it Domain Event Notification, some people call it a public event, but the concept is the same.

The second most important thing is when to publish and process Domain Events? Events may be created after each action on the Aggregate, so we must publish them:

– after each Command handling (but BEFORE committing transaction)

– after each Domain Event handling (but WITHOUT committing transaction)

Last thing to consider is processing of Domain Event Notifications (public events). We need to find a way to process them outside transaction and here Outbox Pattern comes in to play.

The first thing that comes to mind is to publish events at the end of each Command handler and commit the transaction, and at the end of each Domain Event handler only publish events. We can, however, try a much more elegant solution here and use the Decorator Pattern. Decorator Pattern allows us to wrap up our handling logic in infrastructural code, similar like Aspect-oriented programming and .NET Core Middlewares work.

We need two decorators. The first one will be for command handlers:

DomainEventsDispatcherCommandHandlerDecorator public class DomainEventsDispatcherCommandHandlerDecorator<T> : IRequestHandler<T, Unit> where T:IRequest { private readonly IRequestHandler<T, Unit> _decorated; private readonly IUnitOfWork _unitOfWork; public DomainEventsDispatcherCommandHandlerDecorator( IRequestHandler<T, Unit> decorated, IUnitOfWork unitOfWork) { _decorated = decorated; _unitOfWork = unitOfWork; } public async Task<Unit> Handle(T command, CancellationToken cancellationToken) { await this._decorated.Handle(command, cancellationToken); await this._unitOfWork.CommitAsync(cancellationToken); return Unit.Value; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class DomainEventsDispatcherCommandHandlerDecorator < T > : IRequestHandler < T , Unit > where T : IRequest { private readonly IRequestHandler < T , Unit > _decorated ; private readonly IUnitOfWork _unitOfWork ; public DomainEventsDispatcherCommandHandlerDecorator ( IRequestHandler < T , Unit > decorated , IUnitOfWork unitOfWork ) { _decorated = decorated ; _unitOfWork = unitOfWork ; } public async Task < Unit > Handle ( T command , CancellationToken cancellationToken ) { await this . _decorated . Handle ( command , cancellationToken ) ; await this . _unitOfWork . CommitAsync ( cancellationToken ) ; return Unit . Value ; } }

As you can see, in line 16 the processing of a given Command takes place (real Command handler is invoked), in line 18 there is a Unit of Work commit. UoW commit publishes Domain Events and commits the existing transaction:

UnitOfWork public class UnitOfWork : IUnitOfWork { private readonly OrdersContext _ordersContext; private readonly IDomainEventsDispatcher _domainEventsDispatcher; public UnitOfWork( OrdersContext ordersContext, IDomainEventsDispatcher domainEventsDispatcher) { this._ordersContext = ordersContext; this._domainEventsDispatcher = domainEventsDispatcher; } public async Task<int> CommitAsync(CancellationToken cancellationToken = default(CancellationToken)) { await this._domainEventsDispatcher.DispatchEventsAsync(); return await this._ordersContext.SaveChangesAsync(cancellationToken); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class UnitOfWork : IUnitOfWork { private readonly OrdersContext _ordersContext ; private readonly IDomainEventsDispatcher _domainEventsDispatcher ; public UnitOfWork ( OrdersContext ordersContext , IDomainEventsDispatcher domainEventsDispatcher ) { this . _ordersContext = ordersContext ; this . _domainEventsDispatcher = domainEventsDispatcher ; } public async Task < int > CommitAsync ( CancellationToken cancellationToken = default ( CancellationToken ) ) { await this . _domainEventsDispatcher . DispatchEventsAsync ( ) ; return await this . _ordersContext . SaveChangesAsync ( cancellationToken ) ; } }

In accordance with the previously described assumptions, we also need a second decorator for the Domain Event handler, which will only publish Domain Events at the very end without committing database transaction:

DomainEventsDispatcherNotificationHandlerDecorator public class DomainEventsDispatcherNotificationHandlerDecorator<T> : INotificationHandler<T> where T : INotification { private readonly INotificationHandler<T> _decorated; private readonly IDomainEventsDispatcher _domainEventsDispatcher; public DomainEventsDispatcherNotificationHandlerDecorator( IDomainEventsDispatcher domainEventsDispatcher, INotificationHandler<T> decorated) { _domainEventsDispatcher = domainEventsDispatcher; _decorated = decorated; } public async Task Handle(T notification, CancellationToken cancellationToken) { await this._decorated.Handle(notification, cancellationToken); await this._domainEventsDispatcher.DispatchEventsAsync(); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class DomainEventsDispatcherNotificationHandlerDecorator < T > : INotificationHandler < T > where T : INotification { private readonly INotificationHandler < T > _decorated ; private readonly IDomainEventsDispatcher _domainEventsDispatcher ; public DomainEventsDispatcherNotificationHandlerDecorator ( IDomainEventsDispatcher domainEventsDispatcher , INotificationHandler < T > decorated ) { _domainEventsDispatcher = domainEventsDispatcher ; _decorated = decorated ; } public async Task Handle ( T notification , CancellationToken cancellationToken ) { await this . _decorated . Handle ( notification , cancellationToken ) ; await this . _domainEventsDispatcher . DispatchEventsAsync ( ) ; } }

Last thing to do is configuration our decorators in IoC container (Autofac example):

Registering decorators builder.RegisterGenericDecorator( typeof(DomainEventsDispatcherNotificationHandlerDecorator<>), typeof(INotificationHandler<>)); builder.RegisterGenericDecorator( typeof(DomainEventsDispatcherCommandHandlerDecorator<>), typeof(IRequestHandler<,>)); 1 2 3 4 5 6 7 builder . RegisterGenericDecorator ( typeof ( DomainEventsDispatcherNotificationHandlerDecorator <> ) , typeof ( INotificationHandler <> ) ) ; builder . RegisterGenericDecorator ( typeof ( DomainEventsDispatcherCommandHandlerDecorator <> ) , typeof ( IRequestHandler < , > ) ) ;

Add Domain Event Notifications to Outbox

The second thing we have to do is to save notifications about Domain Events that we want to process outside of the transaction. To do this, we use the implementation of the Outbox Pattern:

Save domain event notifications in Outbox var domainEventNotifications = new List<IDomainEventNotification<IDomainEvent>>(); foreach (var domainEvent in domainEvents) { Type domainEvenNotificationType = typeof(IDomainEventNotification<>); var domainNotificationWithGenericType = domainEvenNotificationType.MakeGenericType(domainEvent.GetType()); var domainNotification = _scope.ResolveOptional(domainNotificationWithGenericType, new List<Parameter> { new NamedParameter("domainEvent", domainEvent) }); if (domainNotification != null) { domainEventNotifications.Add(domainNotification as SeedWork.IDomainEventNotification<IDomainEvent>); } } domainEntities .ForEach(entity => entity.Entity.ClearDomainEvents()); var tasks = domainEvents .Select(async (domainEvent) => { await _mediator.Publish(domainEvent); }); await Task.WhenAll(tasks); foreach (var domainEventNotification in domainEventNotifications) { string type = domainEventNotification.GetType().FullName; var data = JsonConvert.SerializeObject(domainEventNotification); OutboxMessage outboxMessage = new OutboxMessage( domainEventNotification.DomainEvent.OccurredOn, type, data); this._ordersContext.OutboxMessages.Add(outboxMessage); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 var domainEventNotifications = new List < IDomainEventNotification < IDomainEvent >> ( ) ; foreach ( var domainEvent in domainEvents ) { Type domainEvenNotificationType = typeof ( IDomainEventNotification <> ) ; var domainNotificationWithGenericType = domainEvenNotificationType . MakeGenericType ( domainEvent . GetType ( ) ) ; var domainNotification = _scope . ResolveOptional ( domainNotificationWithGenericType , new List < Parameter > { new NamedParameter ( "domainEvent" , domainEvent ) } ) ; if ( domainNotification != null ) { domainEventNotifications . Add ( domainNotification as SeedWork . IDomainEventNotification < IDomainEvent > ) ; } } domainEntities . ForEach ( entity = > entity . Entity . ClearDomainEvents ( ) ) ; var tasks = domainEvents . Select ( async ( domainEvent ) = > { await _mediator . Publish ( domainEvent ) ; } ) ; await Task . WhenAll ( tasks ) ; foreach ( var domainEventNotification in domainEventNotifications ) { string type = domainEventNotification . GetType ( ) . FullName ; var data = JsonConvert . SerializeObject ( domainEventNotification ) ; OutboxMessage outboxMessage = new OutboxMessage ( domainEventNotification . DomainEvent . OccurredOn , type , data ) ; this . _ordersContext . OutboxMessages . Add ( outboxMessage ) ; }

As a reminder – the data for our Outbox is saved in the same transaction, which is why At-Least-Once delivery is guaranteed.

Implementing flow steps

At this point, we can focus only on the application logic and does not need to worry about infrastructural concerns. Now, we only implementing the particular flow steps:

a) When the Order is placed then create Payment:

OrderPlacedDomainEventHandler public class OrderPlacedDomainEventHandler : INotificationHandler<OrderPlacedEvent> { private readonly IPaymentRepository _paymentRepository; public OrderPlacedDomainEventHandler(IPaymentRepository paymentRepository) { _paymentRepository = paymentRepository; } public async Task Handle(OrderPlacedEvent notification, CancellationToken cancellationToken) { var newPayment = new Payment(notification.OrderId); await this._paymentRepository.AddAsync(newPayment); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class OrderPlacedDomainEventHandler : INotificationHandler < OrderPlacedEvent > { private readonly IPaymentRepository _paymentRepository ; public OrderPlacedDomainEventHandler ( IPaymentRepository paymentRepository ) { _paymentRepository = paymentRepository ; } public async Task Handle ( OrderPlacedEvent notification , CancellationToken cancellationToken ) { var newPayment = new Payment ( notification . OrderId ) ; await this . _paymentRepository . AddAsync ( newPayment ) ; } }

b) When the Order is placed then send an email:

OrderPlacedNotification public class OrderPlacedNotification : DomainNotificationBase<OrderPlacedEvent> { public OrderId OrderId { get; } public OrderPlacedNotification(OrderPlacedEvent domainEvent) : base(domainEvent) { this.OrderId = domainEvent.OrderId; } [JsonConstructor] public OrderPlacedNotification(OrderId orderId) : base(null) { this.OrderId = orderId; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class OrderPlacedNotification : DomainNotificationBase < OrderPlacedEvent > { public OrderId OrderId { get ; } public OrderPlacedNotification ( OrderPlacedEvent domainEvent ) : base ( domainEvent ) { this . OrderId = domainEvent . OrderId ; } [ JsonConstructor ] public OrderPlacedNotification ( OrderId orderId ) : base ( null ) { this . OrderId = orderId ; } }

OrderPlacedNotificationHandler public class OrderPlacedNotificationHandler : INotificationHandler<OrderPlacedNotification> { public async Task Handle(OrderPlacedNotification request, CancellationToken cancellationToken) { // Send email. } } 1 2 3 4 5 6 7 public class OrderPlacedNotificationHandler : INotificationHandler < OrderPlacedNotification > { public async Task Handle ( OrderPlacedNotification request , CancellationToken cancellationToken ) { // Send email. } }

c) When the Payment is created then send an email:

PaymentCreatedNotification public class PaymentCreatedNotification : DomainNotificationBase<PaymentCreatedEvent> { public PaymentId PaymentId { get; } public PaymentCreatedNotification(PaymentCreatedEvent domainEvent) : base(domainEvent) { this.PaymentId = domainEvent.PaymentId; } [JsonConstructor] public PaymentCreatedNotification(PaymentId paymentId) : base(null) { this.PaymentId = paymentId; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class PaymentCreatedNotification : DomainNotificationBase < PaymentCreatedEvent > { public PaymentId PaymentId { get ; } public PaymentCreatedNotification ( PaymentCreatedEvent domainEvent ) : base ( domainEvent ) { this . PaymentId = domainEvent . PaymentId ; } [ JsonConstructor ] public PaymentCreatedNotification ( PaymentId paymentId ) : base ( null ) { this . PaymentId = paymentId ; } }

PaymentCreatedNotificationHandler public class PaymentCreatedNotificationHandler : INotificationHandler<PaymentCreatedNotification> { public async Task Handle(PaymentCreatedNotification request, CancellationToken cancellationToken) { // Send email. } } 1 2 3 4 5 6 7 public class PaymentCreatedNotificationHandler : INotificationHandler < PaymentCreatedNotification > { public async Task Handle ( PaymentCreatedNotification request , CancellationToken cancellationToken ) { // Send email. } }

The following picture presents the whole flow:

Summary

In this post I described how it is possible to process Commands and Domain Events in a Deep System in a reactive way.

Summarizing, the following concepts has been used for this purpose:

– Decorator Pattern for events dispatching and transaction boundary management

– Outbox Pattern for processing events in separate transaction

– Unit of Work Pattern

– Domain Events Notifications (public events) saved to the Outbox

– Basic DDD Building Blocks – Aggregates and Domain Events

– Eventual Consistency

Source code

If you would like to see full, working example – check my GitHub repository.

Additional Resources

The Outbox: An EIP Pattern – John Heintz

Domain events: design and implementation – Microsoft

Related posts

How to publish and handle Domain Events

Simple CQRS implementation with raw SQL and DDD

The Outbox Pattern