domain-driven-designbusiness-logicdomain-eventshexagonal-architecture

Domain Driven Design (DDD): Domain Event Handlers – Where to place them?


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:

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

Solution

  • 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 as void 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 or application 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.