Best practices to handle Domain Events in DDD

1,062 views
Skip to first unread message

Sebastian

unread,
Oct 14, 2017, 12:06:36 PM10/14/17
to DDD/CQRS
Hi all,

in the last weeks I looked for examples how and where to handle domain events. This seems to be either a rare real world scenario or a problem without a good practice regarding how to structure it and how to do it. 

For simplicity I would like to focus on real domain events (thrown and processed in the same bounded context within its domain area). Most of the times I see examples like this:

Aggregate root entity User 
class User {

   
DomainEvents domainEvents;


   
//...

   
public void login() {
       
this.loggedIn = true;
        domainEvents
.raise(new UserLoggedInEvent(this.getId())
   
}
}

EventHandler for UserLoggedInEvent
class UserLoggedInEventHandler implements EventHandler<UserLoggedInEvent> {
   
   
UserRepository userRepository;
   
OrderRepository orderRepository;
   
// more and more repositories and domain services


    handle
(UserLoggedInEvent loggedInEvent) {
        addToUsersLoggedInHistory
(loggedInEvent)
        analyseLoginForPossibleHijack
(loggedInEvent)
       
//gets more and more over time
   
}


   
private void addToUsersLoggedInHistory(UserLoggedInEvent loggedInEvent) { ... }
   
private void analyseLoginForPossibleHijack(UserLoggedInEvent loggedInEvent) { ... }

}

Hence, to follow the single responsibility principle (SRP) and to match the language of the real business an event is raised.

But I see bad effects:
  • The Event handler handles a lot of different cases in a procedural manner. It will get bigger and bigger over time with a lot of references to other repositories and other services.
  • There are lots of event handlers in one package or a lot of packages in one package. This is due to a common space that contains handler that handles multiple things of multiple aggregates. Over time most of the handlers will access multiple domains. From a more top level domain this also violates the SRP.
  • Adding even more delegations from the handler to for example an entity to handle special events does not make things more clear. Furthermore it adds more complexity and responsibilities to entities and feels also wrong. 
What I think can help is to create event handler for special aggregates like UserLoggedInOrderHandler that is placed in the order aggregate package. This tidies things for me a bit up.

Do you have other good examples, practices, ... to show the handling of domain events in a good way ?

Regards
Sebastian

Rickard Öberg

unread,
Oct 14, 2017, 7:19:35 PM10/14/17
to ddd...@googlegroups.com
2017-10-11 18:01 GMT+08:00 Sebastian <akri...@gmail.com>:
> Do you have other good examples, practices, ... to show the handling of
> domain events in a good way ?

For me the kind of handling you showed doesn't work at all, as my
system is clustered. If all instances do the work examples you had,
and caused side effects, it would get messy.

The way I handle domain events is very simple: each instance in the
cluster subscribes to the event store, gets events, maps to database
updates, and executes those updates. Each instance has a local
database, non-clustered, so that works well. Then, each instance can
answer requests, either reads or writes. If I have jobs that needs to
be done, like the analyseLoginForPossibleHijack, I'll have a cron job
call an endpoint on the system as a whole to trigger it, load balancer
picks an available instance, and the algo runs on the local database
of the selected instance.

That's pretty much it.

/Rickard

Danil Suits

unread,
Oct 16, 2017, 10:03:44 AM10/16/17
to DDD/CQRS

The Event handler handles a lot of different cases in a procedural manner. It will get bigger and bigger over time with a lot of references to other repositories and other services.


I believe the usual answer here is to apply an adapter pattern

class Subscription implements EventHandler<UserLoggedInEvent>{
   
private final Iterable<EventHandler<UserLoggedInEvent> subscriptions;

    handle(UserLoggedInEvent loggedInEvent) {
        subscriptions
.forEach( s -> s.handle(loggedInEvent)
   
}
}

So the subscription would be wired directly to the event handlers that are supposed to run in the same transaction that created the domain event, where those handlers in turn might run immediately, or instead schedule the handling asynchronously.

I know of no particular reason that one event has to have one and exactly one event handlers.

My current thinking is that a subscription is just plumbing - copy a domain event from a (logical) output document here to one or more (logical) input documents there.  Expressing the same idea in a different way - the domain code should be plumbing agnostic.

Christian Droulers

unread,
Oct 16, 2017, 3:24:28 PM10/16/17
to DDD/CQRS
Isn't this what the "Message Bus" supposed to do? Take a thrown event and send it to the appropriate listeners (plural) which can be an in-memory bus in the same context, or asynchronous bus using messaging or scheduling or whatever?

In my case, I use a basic class that asks my DI container for all instances of `IMessageHandler<TEvent>` and call each of them sequentially, but my implementation can be replaced by message queuing later.
Reply all
Reply to author
Forward
0 new messages