I am confused about where to handle domain events in an application that is based on the hexagonal architecture. I am talking about the bounded-context-internal domain events, and not about inter-context integration/application/public events.
Background
As far as I understand, application logic (i.e. use case logic, workflow logic, interaction with infrastructure etc.) is where command handlers belong, because they are specific to a certain application design and/or UI design. Command handlers then call into the domain layer, where all the domain logic resides (domain services, aggregates, domain events). The domain layer should be independent of specific application workflows and/or UI design.
In many resources (blogs, books) I find that people implement domain event handlers in the application layer, similar to command handlers. This is because the handling of a domain event should be done in its own transaction. And since it could influence other aggregates, these aggregates must be loaded via infrastructure first. The key point however, is this: The domain event is torn apart and turned into a series of method calls to aggregates. This important translation resides in the application layer only.
Question
I consider the knowledge about what domain events cause what effects on other aggregates as an integral part of the domain knowledge itself. If I were to delete everything except my domain layer, shouldn't that knowledge be retained somewhere? In my view, we should place domain event handlers directly in the domain layer itself:
They could be domain services which receive both a domain event and an aggregate that might be affected by it, and transform the domain event into one or many method calls.
They could be methods on aggregates themselves which directly consume the entire domain event (i.e. the signature contains the domain event type) and do whatever they want with it.
Of course, in order to load the affected aggregate, we still need a corresponding handler in the application layer. This handler only starts a new transaction, loads the interested aggregate and calls into the domain layer.
Since I have never seen this mentioned anywhere, I wonder if I got something wrong about DDD, domain events or the difference between application layer and domain layer.
EDIT: Examples
Let's start with this commonly used approach:
// in application layer service (called by adapter)
public void HandleDomainEvent(OrderCreatedDomainEvent event) {
var restaurant = this.restaurantRepository.getByOrderKind(event.kind);
restaurant.prepareMeal(); // Translate the event into a (very different) command - I consider this important business knowledge that now is only in the application layer.
this.mailService.notifyStakeholders();
}
How about this one instead?
// in application layer service (called by adapter)
public void HandleDomainEvent(OrderCreatedDomainEvent event) {
var restaurant = this.restaurantRepository.getByOrderKind(event.kind);
this.restaurantDomainService.HandleDomainEvent(event, restaurant);
this.mailService.notifyStakeholders();
}
// in domain layer handler (called by above)
public void HandleDomainEvent(OrderCreatedDomainEvent event, Restaurant restaurant) {
restaurant.prepareMeal(); // Now this translation knowledge (call it policy) is preserved in only the domain layer.
}
The problem with most even handler classes is that they often are tied to a specific messaging technology and therefore often placed in the infrastructure layer.
However, nothing prevents you to write technology-agnostic handlers and use technology-aware adapters that dispatches to them.
For instance, in one application I've built I had the concept of an Action Required Policy. The policy drove the assignment/un-assignment of a given Work Item to a special workload bucket whenever the policy rule was satisfied/unsatisfied. The policy had to be re-evaluated in many scenarios such as when documents were attached to the Work Item, when the Work Item was assigned, when an external status flag was granted, etc.
I ended up creating an
ActionRequiredPolicy
class in the domain which had event handling methods such asvoid when(CaseAssigned event)
and I had an even handler in the infrastructure layer that simply informed the policy.I think another reason people put these in the
infrastructure
orapplication
layers is that often the policies react to events by triggering new commands. Sometimes that approach feels natural, but some other times you want want to make it explicit that an action must occur in response to an event and otherwise can't happen: translating events to commands makes it less explicit.Here's an older question I asked related to that.