Handling domain events

Last time I wrote a post about the commands. Events are quite similar to the commands – the main difference between these 2 patterns is that the events are related to the things that have actually happened to our entity e.g. by invoking the command. They’re also a core part of the DDD (Domain Driven Design) and can be easily implemented within our software solution.


 

Let’s define our event interface at first:

Now, we can move further and take a look into the base Entity class:

In case you were wondering why I’m using a dictionary here – thanks to this type of a collection I can avoid adding multiple events of the same type e.g. UserEmailChanged and then invoking them multiple times. Yet, again – it’s totally up to you, so feel free to choose the preferable solution.

So far so good, eventually we can define our event once the user’s email was changed.

Remember that you’d better keep your events very thin. Sure thing, you could pass the whole user object, but think outside of the box – what if you want to scale your application on multiple servers? By passing the minimum amount of the information required to identify the entity (e.g. it’s id and maybe an old value along with the new one) you will be able to push such event by message bus or any other communication protocol and handle easily by some external application, without worrying about (de)serialization issues and huge traffic due to the large objects.

And here’s our user:

As you can see, whenever the user changes the email address, we will store such event.
And here comes to the question, how to handle this event? Well, quite easily, so let’s start with the event handler definition:

And the event dispatcher based on the Autofac IoC container:

Speaking of the Autofac, we have to register our handlers and dispatcher, so that IoC magic will be able to happen:

And that’s pretty much it. What you can do now, is, for example, the following:

My approach to the events is mostly based on the Jimmy Bogard’s post which is an opposite to the static pattern described by the Udi Dahan here.

25 Comments Handling domain events

  1. Pingback: Handling domain events - How to Code .NET

  2. Philipp

    var method = handler.GetType()
    .GetRuntimeMethods()
    .First(x => x.Name.Equals(“HandleAsync”));

    This will not work if a handler implements multiple IEventHandler interfaces. This can be a valid possibility. E.g. Sagas that mutate the state on multiple different events.
    Its better to do it like this: ((dynamic)handler).HandleAsync(@event)
    (because you get the handler using the interface you know that method exists)

    Reply
  3. Pingback: Dew Drop - August 1, 2016 (#2299) - Morning Dew

  4. Raham Rahim

    Excellent article, but wouldn’t make more sense to use a ConcurrentDictionary instead of a plain Dictionary?

    Reply
    1. Piotr Gankiewicz

      Hi Jernej,
      In my scenario, I publish the events after the transaction has been completed – for example within the IUserService -> CreateAsync(); dispatching events is the last thing that happens only after the particular operation (e.g. creating and saving a new user in the database) was successfully completed. I hope that it does answers your question (rolling back the transaction would prevent invoking the event dispatcher).

      Reply
  5. Pingback: "Use your feelings, Obi-Wan, and find Links you will." – Yoda - Magnus Udbjørg

  6. jernej

    Makes sense. But I forgot to mention I’m working with a long living db context (desktop app). So after rolling back I would need to clear the _events collection in every entity that participated in the failed transaction whereas if you are returning events from commands, you are not mutating states of any entities.

    Reply
  7. jernej

    One cool side effect when returning events from commands is that you can have a EventDispatcherCommandHandler that you can wrap arround a TransactionalCommandHandler and let the command handling pipeline automatically dispatch events so you don’t need to dispatch them manually from application services 🙂

    Reply
  8. Pingback: Zbudujemy nowy DOM – Dev on board

  9. Zomat

    You used 2 constructor for User class. One of them is paremeterless and other gets email parameter. Do you want to take precautions to don’t send email while update data?

    Reply
  10. 0xmarcin

    I agree that event classes should be small and contain only serializable state, but in case of UserEmailChanged shouldn’t we also include old and new value of user email. Let’s consider situation in which user quickly changes emails from foo1@example.com to foo2@example.com and to foo3@example.com – then if our infrastructure responsible for event processing will not be fast enough we may lost information about first change. Storing additional data in the event also will allow us to more easily debug production issues (if we save event’s somewhere of course).

    Reply
    1. Piotr Gankiewicz

      Of course, this is a good example and I also do it like this. A few properties are good enough, we just have to keep in mind that passing nested objects via service bus is rather not a best choice.

      Reply
  11. Igor Kuznetsov

    With Autofac extension generic method ResolveOptional we can do instead of this:
    var eventType = @event.GetType();
    var handlerType = typeof(IEventHandler).MakeGenericType(eventType);
    object handler;
    _context.TryResolve(handlerType, out handler)
    if (handler == null)
    return;
    var method = handler.GetType()
    .GetRuntimeMethods()
    .First(x => x.Name.Equals(“HandleAsync”));
    await (Task) ((dynamic) handler).HandleAsync(@event);

    just this:
    var handler =_context.ResolveOptional<IEventHandler<T>>();
    if (handler == null) return;
    await handler.HandleAsync(@event);

    Reply
    1. Igor Kuznetsov

      A little mistake in my comment. ResoveOptional should be called with type argument IEventHandler:
      var handler =_context.ResolveOptional<IEventHandler>();

      Reply
      1. Igor Kuznetsov

        oh, now I see, that’s is not my mistake. Type argument was automatically removed . Type argument should be “IEventHandler” (remove underscore)

        Reply

Leave A Comment

Your email address will not be published. Required fields are marked *