6

Domain objects shouldn't have any dependencies, hence no dependency injection either. However, when dispatching domain events from within domain objects, I'll likely want to use a centralised EventDispatcher. How could I get hold of one?

I do not want to return a list of events to the caller, as I'd like them to remain opaque and guarantee their dispatch. Those events should only be consumed by other domain objects and services that need to enforce an eventual consistent constraint.

Double M
  • 1,449
  • 1
  • 12
  • 29

3 Answers3

2

See Udi Dahan's domain events

Basically, you register one or more handlers for your domain events, then raise an event like this:

public class Customer
{
   public void DoSomething()
   {
      DomainEvents.Raise(new CustomerBecamePreferred() { Customer = this });
   }
}

And all the registered handler will be executed:

public void DoSomethingShouldMakeCustomerPreferred()
{
   var c = new Customer();
   Customer preferred = null;

   DomainEvents.Register<CustomerBecamePreferred>(p => preferred = p.Customer);

   c.DoSomething();
   Assert(preferred == c && c.IsPreferred);
}

This is basically implementing Hollywood Principle (Don't call us, we will call you), as you don't call the event handler directly - instead the event handler(s) get executed when the event is raised.

Hooman Bahreini
  • 14,480
  • 11
  • 70
  • 137
  • 1
    That's exactly what I was looking for. Most notable is the circumstance that in this case, a static reference is ok and won't obstruct testability as explained in the linked article. – Double M Oct 26 '18 at 05:41
  • 1
    The static `DomainEvents` is a form of Ambient Context. This is an anti-pattern and Udi Dahan has, for that reason, long abandoned this static class for publishing events. You should prefer @VoiceOfUnreason's answer. – Steven Oct 27 '18 at 09:19
  • 1
    @Hooman: I'm unsure whether Udi has a complete blog on this. I've been searching for the past hour to find out the source, but I can't find it. So, unfortunately, I can't prove this. He made this statement in 2012 or possibly even earlier. Hopefully someone else can provide us with a link. There is a long description, however, in chapter 5.3 of [DI PP&P](https://manning.com/seemann2) that explains why Ambient Context is an anti-pattern. – Steven Oct 27 '18 at 10:43
  • 1
    That's not the comment I am talkig about, because he is not talking about the usage of this static class. Saga is another word for the Process Manager pattern and it is an orchestrator for handling domain events. So he is not disadvising to publishing domain events at all. – Steven Oct 28 '18 at 07:59
  • I found a comment by Udi Dahan [comment number 210 at the bottom of this page](http://udidahan.com/2009/06/14/domain-events-salvation/) about domain events, he says: "These days, instead of recommending domain events I prefer to model things as sagas with NServiceBus. I find that it simplifies things greatly – effectively turning the saga into an instance of the domain model pattern, where the events are regular NServiceBus events." – Hooman Bahreini Mar 05 '19 at 21:02
2

I'll likely want to use a centralised EventDispatcher. How could I get hold of one?

Pass it in as an argument.

It probably won't look like an EventDispatcher, but instead like some Domain Service that describes the required capability in domain specific terms. When composing the application, you choose which implementation of the service to use.

VoiceOfUnreason
  • 52,766
  • 5
  • 49
  • 91
  • 1
    Pas it as an argument where? Passing a `Dispatcher` to every AR methods seems very tedious to me if that's what you mean. I'd prefer a static instance or simply recording events on the AR like you'd do in ES. – plalx Oct 26 '18 at 00:16
1

You are asking to have it both ways. You either need to inject the dependency or invert control and let another object manager the interaction between Aggregate and EventDispatcher. I recommend keeping your Aggregates as simple as possible so that they are free of dependencies and remain testable as well.

The following code sample is very simple and would not be what you put into production, but illustrates how to design Aggregates free of dependencies without passing around a list of events outside of a context that needs them.

If your Aggregate has a list of events within it:

class MyAggregate 
{
    private List<IEvent> events = new List<IEvent>();

    // ... Constructor and event sourcing?

    public IEnumerable<IEvent> Events => events;

    public string Name { get; private set; }

    public void ChangeName(string name)
    {
        if (Name != name) 
        { 
            events.Add(new NameChanged(name); 
        }
    }
}

Then you might have a handler that looks like:

public class MyHandler 
{
    private Repository repository;

    // ... Constructor and dependency injection

    public void Handle(object id, ChangeName cmd)
    {
        var agg = repository.Load(id);
        agg.ChangeName(cmd.Name);
        repository.Save(agg);
    }
}

And a repository that looks like:

class Repository
{
    private EventDispatcher dispatcher;

    // ... Constructor and dependency injection

    public void Save(MyAggregate agg)
    {
        foreach (var e in agg.Events)
        {
            dispatcher.Dispatch(e);                
        }
    }
}
CPerson
  • 1,222
  • 1
  • 6
  • 16
  • hmm... so I'd have to keep a `public` list of all events that happened within the transaction. Is that how it's usually done? – Double M Oct 25 '18 at 19:01
  • @DoubleM It's list of events that were generated by that particular Aggregate. This is the approach I take. I am able to test Aggregates without the concept of EventDispatcher. Simple domain logic. Alternatively, you can raise events from the Aggregate and have the Repository subscribe and keep the list internally. The idea is to keep the Aggregate free of dependencies. To go one step further, the events are not dispatched in the scope of the transaction. They are written to EventStore and then read from there and dispatched by a separate process. – CPerson Oct 25 '18 at 19:43
  • "Alternatively, you can raise events from the Aggregate [...]" - that was my original question. How? Not without a static dependency to a dispatcher, right? I take it you see the events very much in the context of event sourcing rather than mere domain events. – Double M Oct 25 '18 at 20:55
  • what do you mean by "You either need to inject the dependency or invert control"? [DI is a form of IoC](https://stackoverflow.com/questions/6550700/inversion-of-control-vs-dependency-injection) – Hooman Bahreini Oct 25 '18 at 23:10
  • @DoubleM I am looking at it from the lens of event sourcing, but you could do the same even if you are not event sourcing. When I said you can "raise events", I meant in the sense that your repository can subscribe to events ("events" in the pub-sub sense) that are published by your aggregate. The subscription would happen at the time you load the aggregate within the repository. You admit you see the issue with injecting an EventDispatcher. And having a static EventDispatcher is also clearly a bad practice. I am proposing an option that avoids both of those. – CPerson Oct 26 '18 at 13:55
  • @Hooman I meant that instead of having the logic of dispatching events within the Aggregate, the repository can drive that logic. DI is one form of IoC, but not the only one. – CPerson Oct 26 '18 at 14:03
  • @CPerson I understand and yours is a fine solution, however Udi Dahan makes a good point that a static reference isn't all that bad **in this case**. I'd rather have my domain object fire events directly than having to depend on the repository to do it (or not!). Also I can then fire immediately or queue for the end of the transaction, my choice. – Double M Oct 27 '18 at 07:56
  • 1
    @DoubleM I think what Udi was showing is that you can workaround the limitations of a static reference. So long as you remember to clear global state. I am weary of tying my domain to a particular commercial vendor, especially when the cost of avoiding that lock-in is low and the price of removing it is high (over a large, and well developed domain). Additionally, should your domain be a pure business rules engine or should it be a message dispatching layer as well? – CPerson Oct 29 '18 at 14:56