DDD - Part Three - Domain Events
Domain event is a very useful concept in Domain Driven Design that allows you to separate concerns in your domain via events. You can also offload the side effects of the actions in your domain to an event handler and let that run asynchronously. For things like sending emails and notifications this makes perfect sense since those are inherently asynchronous anyway.
Nitpicker’s Corner: I understand that if you can live with Eventual Consistency, you can do pretty much everything in your domain with events and commands. In this post, I’m assuming Eventual Consistency isn’t what you are after and you want a transactional boundary.
If you ask what is exactly a Domain Event, here’s Marting Fowler’s definition:
The essence of a Domain Event is that you use it to capture things that can trigger a change to the state of the application you are developing. These event objects are then processed to cause changes to the system…
Depending on your domain, sometimes you can’t live with inconsistencies (or eventual consistency), so the events you raise should be handled in the same unit of work as the action on your Aggregate Root. Having said that, sometimes it conceptually makes sense to have an asynchronous event handler run after the main transaction is committed (and we’ll come back to this). Things like sending emails or communicating with a third party system which is inherently asynchronous and network bound can be done in this way. Let’s see how both can be done.
Since these events run in the same unit of work, they are very easy to manage due to lack of eventual consistency. Essentially, if you roll back the main transaction, the events that are raised are rolled back as well, as if nothing has happened. Let’s see an example:
public class Order : AggregateRoot
This is basically the gist of it, but there are a few details that you may miss at first glance. First, from performance aspects, since this runs within the same transaction (as I said the event handler is not asynchronous in this example) and we have a long running operation, there is higher chances of deadlocks and timeouts, but hey, the benefit is having consistency within the boundary of the transaction scope.
The main thing here, is that it is not clear ‘how’ the event is raised. What’s the magic to the ‘Raise’ method? The Raise method is simple really, it just adds the events you give to it to an in-memory collection of events. So who raises the event, I hear you ask? This is done somewhere in the bowls of our DbContext. Ideally, we want the event to be raised only when the object graph is persisted in the database, but the transaction is still not committed so that we can roll back, if need be. This way we can do our event processing and database interactions in one transaction to get consistency. Here’s the missing piece of the puzzle:
public class DomainEvents
The other thing to note here is that since the event processing will be done in the same transaction, the more you add handlers for an event, the more time it is going to take to complete a transaction. What’s even worse is that it may not be obvious - due to decouple nature of event and event handler - that you’re adding more work to your transactions.
It is not all bad though. If you can live with Eventual Consistency, you can offload some your event handlers to work asynchronously. This means, even the main work is done within the transaction and the side effects are done in subsequent / separate transactions. On next post we’ll see how this works.