Consider the following requirement, that is quite common for many systems:
When a user changes his email address, he should receive an email with a confirmation link.
The “user” will probably be an aggregate root in our domain and have an attribute for its email. Code for sending an email, however, is not a responsibility of our domain, it’s an infrastructure concern. Since domain model should not reference any infrastructure code, we need a way to call email service indirectly. The “when..then..” nature of the requirement makes us consider using Domain Events for this task – we can create a Domain Event and raise it when user’s email changes. This way, we shift the responsibility of actually sending an email to the Domain Event handler.
If you look for a Domain Event dispatching implementation in .NET, you will probably find Udi Dahan’s post. That’s the first implementation that I have ever used in a real application. Notice that it calls the handler directly. As a result, event handlers are executed in the same thread and transaction as the code that raises it. Jimmy Bogard’s solution basically works the same way, but eliminates the static reference and delays raising the events until entity changes are saved to repository.
The following code illustrates a possible implementation of the above requirement using Udi Dahan’s approach:
public class UserEmailChanged
{
public long UserId { get; private set; }
public EmailAddress NewEmailAddress { get; private set; }
public UserEmailChanged(long userId, EmailAddress newEmailAddress)
{
UserId = userId;
NewEmailAddress = newEmailAddress;
}
}
...
public void ChangeEmailAddress(EmailAddress newAdress)
{
this.EmailAddress = newAddress;
this.EmailAddressConfirmed = false;
DomainEvents.Raise(new UserEmailChanged(this.Id, this.EmailAddress));
}
...
public class UserEmailChangedHandler: IHandle<UserEmailChanged>
{
public void Handle(UserEmailChanged event)
{
smtpServer.Send(...);
}
}
At this point, there is a problem. If UserEmailChangedHandler
is invoked synchronously at the time of raising UserEmailChanged
event, the email message is sent before changes to the entity are saved to a repository. Email sending operation does not participate in transaction and cannot be rolled back. If we get an exception after the event was raised but before changes are saved to data store, user gets an email and when he clicks the link he gets an error. That’s not a very good user experience. Can it be prevented?
If we changed the code, so that events would be raised after changes are saved to data store (transaction is committed), would it be better? Probably not. If email sending operation fails for some reason (timeout, SMTP server down, etc.), we’ve saved entity state for later, but lost our domain event. No email will be sent. Depending on your application, it might be acceptable. If failures like these are quite rare and there’s a possibility in UI to request a new activation email, that might be a viable solution. If this is not acceptable though, we need to think a bit more.
Event or Handler?
Since we see transactional and non-transactional behavior related to Domain Events, does that mean that there are two different types of events? That might seem true at first, but if you think about it, the event itself has no idea about how it will be handled. For example, if we changed our requirements and removed the confirmation email, we would have no more problems with non-transactional email sending.
On the other hand, the handlers do know if they’re transactional or not. But how should an implementation of a handler with non-transactional behavior look like to avoid problems?
In the comments of the previously mentioned post, Udi Dahan pointed out that usually his handlers just send a one-way message to another endpoint, using transactional means. And really, looking back at a project where I (and my team, of course) used Domain Events most, almost all handlers were just publishing an event further through ESB, using transactional means.
In email sending case, the handler could send a message to some kind of messaging sub-system that will send an email asynchronously. The Domain Event handler itself would execute in a transaction. This means, that if we encounter an error, message is not sent anywhere. The event didn’t happen, no email would be sent.
Handling Domain Events Asynchronously
In the more recent book on DDD, “Implementing Domain-Driven Design” by Vaughn Vernon, he says that:
When Events are delivered to interested parties, in either local or foreign systems, they are generally used to facilitate eventual consistency. This is purposeful and by design. It can eliminate the need for two-phase commits (global transactions) and support of the rules of Aggregates.
This basically answers all the questions. Domain Events are a tool to achieve eventual consistency, they are meant to be handled asynchronously. The safest way to use Domain Events is for the publisher to put them to a store when they are raised and later handle them on another thread or even process.
If we look at the initial problem of sending an email from this perspective, do we really need to actually send the email as a part of changing user’s email address? We don’t. What we really care about is initiating email sending process and that can be done by raising the event and storing it for later processing.
Domain Event Storage
Of course, in real life, there are many ways to store a domain event for later processing. We can use anything that is durable and supports transactions. Here are some possible options:
- Database. If you are using a database for storing your data, you can also use it for storing the events in a separate table (for relational databases) or document collection (for document databases). The good thing about this approach is that if you use the save database for domain model data and event storage, no distributed transactions will be needed. That will give you better performance.
- Message Queue. Durable and transactional message queues (like MSMQ, for example) enable you to safely put events into a queue for later handling. You can even send it to another machine, if needed.
- Sending over an ESB. If you are using a service bus and it supports transactions, you can call it directly to publish the event to other subsystems. For example,
NServiceBus
works on top of MSMQ out of the box, so you get the benefits of a message queue here.
You should pick the one that works best for you.
Implementation
The implementation of these ideas highly depends on your situation:
- If all your event handlers publish a message over an ESB, you can safely use a Domain Events publisher that directly invokes the handlers. This way, you do not need to store and process the events asynchronously.
- If your handlers contain non-transactional operations or change other Aggregate Roots in your domain model, consider storing the events when they are published and handling them in an asynchronous way. You can, for example, directly store all events on publishing and have a separate thread that pulls an event from a store, invokes a corresponding handler and deletes the event from the store on success. This might be more complex to implement since you need a separate thread, but will save you some head-scratching later. It also allows to scale better, since you can move processing of events to other machines quite easily.
Conclusion
Domain Event handlers that are called synchronously should always be transactional. If that’s not possible, consider saving events to a store for later processing or sending them over an ESB, all in a transaction. This approach lets you handle failures and scale better also goes hand in hand with aggregate’s rule of thumb of not modifying more than a single aggregate in a single domain operation.