You can find a good primer on domain events in this post by Udi Dahan. There are some issues with his approach, though that Jimmy Bogard raises and addresses in his post. However, I was left with two questions:
- Shouldn’t the domain event be dispatched/handled only when the transaction or the unit-of-work commits? Because whatever changes have been made to the state of the domain isn’t really permanent until that happens.
- There may be cases when domain events need to trigger changes to other domain objects in the same bounded context – and all of that needs to be persisted transactionally. In other words, in this scenario – it makes sense to have the event be dispatched just before the transaction commits. However, in this case, whatever ends up handling that event also needs access to the current transaction or unit-of-work that is in play – so that all the changes make it to persistence in one fell swoop of a commit.
That leads me to conclude that there are really two types of domain events that need to be handled differently. The first type as listed above would either be infrastructure-y things like sending out e-mails and such, or sending messages to other bounded contexts or external systems. The second type would be within the same bounded context but maintain certain kinds of relations within the domain that could not be modeled within the same aggregate (simply put, they take the place of database triggers in the DDD metaphor).
At this point, I have no further design level ideas on how they would be modeled. More on that later, hopefully. I do know that the thing raising the event should not need to know what kind of domain event it is going to raise. If I am a Foo and my Bar changes, all I care about is I need to raise a BarChanged event. I do not need to know what kind of domain event BarChanged is. I will let this percolate a bit.
There is a lot of discussion online around whether DDD and SOA can co-exist, and if so, what that looks like. I am of the opinion that they can co-exist and have arrived at a model that seems to work for me. Consider a complex DDD system with several bounded contexts and contrast it to an SOA system – and I am including the flavor of SOA that I describe in this post.
So, how do the two come together? As I have mentioned before, one problem I have with straight-up SOA is that there is no identifiable “core” of the system where the “meat” is. This “meat” is in fact – the domain logic. Now, add to this the fact that the domain is the only thing that deals directly with the database (technically, it’s the infrastructure layer that handles persistence – but you get the idea – it is the domain constructs that are involved in persistence to/from a data store via repositories).
What if we modeled the entire domain as a service? It does not need to be strictly hosted – think of it as a service that is consumed by other services as a library. The repositories combined with the domain objects themselves can replace the accessor/persistor class of services. This makes sense in terms of volatility as well – since the domain really needs to be the part that is least volatile. You can then think of the application layer and the infrastructure layer as being made up of all the other services – and these would still follow the whole utility / computational service / use case handler hierarchy. You would not allow the domain layer to call utility services though – but that is something you would handle through domain events and the like. The presentation layer is still the presentation layer – and is where the UI lives, or the external hosting interface to your services.
The mode of communication between bounded contexts does not change. You can now think of each bounded context as its own SOA subsystem. Communication within bounded contexts would still happen through queues, messages and subscriptions. The application and infrastructure layer would also have “domain event handler” services that handle domain events – I guess you could argue that is just one type of a use case handler. The communication between bounded contexts, as mentioned earlier, stays the same.