4

I read that people use event dispatching libraries for domain events in their domain-driven design.

The C# language have built-in support for events using the event keyword together with the EventHandler<> class. Is it feasible to use this instead of a event dispatcher library (such as MediatR)?

I know that domain events are often dispatched when persisted, not when the method on the aggregate is called. But by adding the events to a List<Action> you can defer the raising of events.

Event declaration:

public event EventHandler<InvoiceCreatedEventArgs> InvoiceCreated;

Deferred event raising:

private ICollection<Action> _events = new List<Action>();

public void AddDomainEvent(Action action)
{
    _events.Add(action);
}

protected virtual void OnInvoiceCreated(InvoiceCreatedEventArgs e)
{
    AddDomainEvent(() => { InvoiceCreated?.Invoke(this, e); });
}

If the events are exposed as public members of the root aggregate then the application would have to resubscribe for every new instance of the root aggregate whenever a root aggregate was fetched from the repository.

Wouldn't that be a bit of a undesirable trait to have to resubscribe for every instance? Would the application have to unsubscribe too?

Unless the events were declared as static, but I have heard bad things about static events and memory leaks. Would this be a concern?

If C# events were used, would they belong in the root aggregate or would they belong in the repository?

If the events were declared in the repository (which could be a Entity Framework Core DbContext) then when registered with the ASP.NET Core dependency handler using the .AddDbContext method it would be registered with the "Scoped" lifetime (once per client request), so unless the events were to be declared as static then the application would have to resubscribe on every new instance of the repository which would occur at every new incoming HTTP request.

Is using C# events for domain events in a application employing domain-driven design feasible or is it just an non-viable ill-fated idea?

Fred
  • 12,086
  • 7
  • 60
  • 83

2 Answers2

0

Without distinguishing between C# Events and Domain Events it’s difficult to follow your narrative. However, if I understand your proposal correctly, you’re envisioning a C# component that listens to the Entity Event stream, then publishes those incoming domain events via C# events to listeners in the app. If that’s what you’re proposing then you would have to work hard to make it work, and it wouldn’t work well.

If we look at Mediatr, it subscribes to an event source and creates new command processors to process incoming domain events. The key is that it creates new command processors so it is able to call a method on them. Also, there is a one-to-one correspondence between a domain event and a command processor, and nothing is required at system startup other than Mediatr itself.

With C# Events the command processor is created by something, then registers itself to receive C# events of a particular type. The command processor initiates the link, not the event source subscriber. In order to process all kinds of events, at startup you would have to create at least one of each type of command processor and let it register itself as the receiver of the C# messages which carries the Domain Event as a payload.

Then what happens when you start to scale? Mediatr scales well because it creates a command processor for each Domain Event. Your proposal would not scale because to process 2 of the same event type you would need to manually create 2 command processors, and each of those command processors would receive BOTH of the incoming Domain Events because they are both subscribed to the same C# Event.

It is possible to code around all this mess, but that’s what Jimmy Bogard has already done. Instead of rewriting all that just fire up NuGet, pull down Mediatr, and then go play with your kids with all the time you saved.

Brad Irby
  • 2,397
  • 1
  • 16
  • 26
  • No, not a entity stream listener. The aggregate has methods that raise C# events. There would be a C# `event` member for each Domain Event, so if one event had 2 subscribers, then the event would be raised once, and received by both handlers only once. – Fred Nov 11 '19 at 12:55
  • So this is only for non-persisted events generated within the app? Say I'm a component that wants to know about any password changes. I need to be notified each time a class is instantiated that can change the password so I can register my event handler with that class. This means publishers need to be able to discover all possible listeners at run time, so it can tell those listeners they need to listen. Is that right? – Brad Irby Nov 11 '19 at 16:55
  • Well it depends if the methods in the aggregate raise the event immediately or if the aggregate defers the raising by adding it to a list for later raising. The "publisher" is the domain method, it just adds an event that gets raised when the aggregate gets persisted and after that all event subscribers get notified. – Fred Nov 11 '19 at 19:13
0

It depends on the type of application.

If the application is a web application then it have a DbContext scoped to the life time of the HTTP request, and all the aggregates have a short life time too that lasts for the duration of the HTTP request. Hence registering the C# event handlers is cumbersome, also since you would have to do it after you fetch get the DbContext or the aggregate it would have to be inside the controller, repeatedly, everywhere. So for web applications using C# for events is a poor choice, and it a much better idea to delegate it to a singleton class as is done with MediatR.

If the application maintains a persistent DbContext that survives for the lifetime of the application and a root aggregate that survives for the lifetime of the application then you can could use C# events and just register the event handlers once. Such an application could be a command-line application, a background service, or a desktop application with a UI.

Fred
  • 12,086
  • 7
  • 60
  • 83