Skip to Content


Post: Handling Domain Events: Missing Part

Handling Domain Events: Missing Part


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:

Shallow system in context of DDD

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:

Deep system in context of DDD


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.


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;

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);

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();

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

// Registering decorators


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>);

    .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(

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);

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;

    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.

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;

    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.

The following picture presents the whole flow:

Flow of processing


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


Related posts See all blog posts

Attributes of Clean Domain Model
28 October 2019
There is a lot of talk about clean code and architecture nowadays. There is more and more talk about how to achieve it. The rules described by Robert C. Martin are universal and in my opinion, we can use them in various other contexts. In this post I would like to refer them to the context of the Domain Model implementation, which is often the heart of our system. We want to have a clean heart, aren't we?
Read More
How to publish and handle Domain Events
11 October 2018
Domain Event is one of the building blocks of Domain Driven Design. It is something that happened in particular domain and it captures memory of it. We create Domain Events to notify other parts of the same domain that something interesting happened and these other parts potentially can react to.
Read More
Modular Monolith: Integration Styles
26 July 2020
In this post, I would just like to discuss the missing part – Integration Styles for modules in Modular Monolith architecture.
Read More
Simple CQRS implementation with raw SQL and DDD
4 February 2019
In this post I wanted to show you how you can quickly implement simple REST API application with CQRS using the .NET Core.
Read More
The Outbox Pattern
11 March 2019
Sometimes, when processing a business operation, you need to communicate with an external component in the Fire-and-forget mode. The question that arises is whether we are able to guarantee the atomicity of our business operation from a technical point of view?
Read More