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

7.3k views Asked by At

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

There are 4 answers

2
plalx On BEST ANSWER

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.

1
Levi Ramsey On

Your description sounds very much like event-sourcing.

If event-sourcing (the state of an aggregate is solely derived from the domain events), then the event handler is in the domain layer, and in fact the general tendency would be to have a port/adapter/anti-corruption-layer emit commands; the command-handler for an aggregate then (if necessary) uses the event handler to derive the state of the aggregate, then based on the state and the command emits events which are persisted so that the event handler can derive the next state. Note that here, the event handler definitely belongs in the domain layer and the command handler likely does, too.

More general event driven approaches, to my mind, tend to implicitly utilize the fact that one side's event is very often another side's command.

It's worth noting that an event is in some sense often just a reified method call on an aggregate.

30
choquero70 On

I follow this strategy for managing domain events:

First of all, it is good to persist them in an event store, so that you have consistence between the fact that triggered the event (for example, a user was created) and the actions it triggers (for example, send an email to the user).

Assuming we have a command bus:

  • I put a decorator around it that persists the events generated by the command.
  • A worker processes the event store and publish the events outside the bounded context (BC).
  • Other BCs (or the same that published it) interested in the event, subscribe to it. The event handers are like command handlers, they belong to the application layer.

If you use hexagonal architecture, the hexagon is splitted into application layer and domain.

9
R.Abbasi On

I can think of one reason to not use domain models as event handlers. In the Application layer, you may have to do some process on the event and then apply it to the domain. Let's say you have some orchestration in that event handler.

On the other hand, I think of the domain model as it is causal-ignorant (if that is an expression!). "I do not care about what happened outside of me, tell me what you want me to do. It may be an event that triggered the action or a command. I'm not interested in the source of action."

I think it may be valid to create an object for every action in the domain model to encapsulate the action's arguments, but it should not reflect the root of the action.

For example "ChangePriceArguments" is valid but "PricePolicyChangedDomainEvent" is not.

This link from Microsoft microservices patterns blogs, explains the difference between domain event and integration event and suggests that:

Therefore, the application layer level is where you should have domain event handlers triggering actions when a domain event is raised.