Domain Events without Service Locator
Asked Answered
L

3

7

Giving the default implementation of Domain Events:

Interface that represents an domain event:

public interface IDomainEvent { }

Interface that represents a generic domain event handler:

public interface IEventHandler<T> where T : IDomainEvent

Central access point to raise new events:

public static class DomainEvents
{
    public static void Raise<T>(T event) where T : IDomainEvent
    {
        //Factory is a IoC container like Ninject. (Service Location/Bad thing)
        var eventHandlers = Factory.GetAll<IEventHandler<T>>();

        foreach (var handler in eventHandlers )
        {
            handler.Handle(event);
        }
    }
}

Consuming:

public class SaleCanceled : IDomainEvent
{
    private readonly Sale sale;

    public SaleCanceled(Sale sale)
    {
        this.sale = sale;
    }

    public Sale Sale
    {
        get{ return sale; }
    }
}

Service that raises the event:

public class SalesService
{
     public void CancelSale(int saleId)
     {
          // do cancel operation

          // creates an instance of SaleCanceled event

          // raises the event
          DomainEvents.Raise(instanceOfSaleCanceledEvent);
     } 
}

Is there another approach to use Domain Events without use of Service Location anti-pattern?

Lien answered 10/3, 2015 at 16:43 Comment(0)
M
7

I guess in your case you really do not have to. Using dependency injection you could inject a IDomainEventDispatcher implementation into your service.

The reason why I think a singleton like this has made it into the mainstream it was one of the first implementations to be proposed by some prominent developers and at first it doesn't feel too wrong. The other reason is that events may need to be raised from within the domain:

public class Customer
{
    public void Enable()
    {
        _enabled = true;

        DomainEvents.Raise(new CustomerEnabledEvent(_id));
    }
}

At some stage I came across this post by Jan Kronquist: http://www.jayway.com/2013/06/20/dont-publish-domain-events-return-them/

This is the third time I have added that link to my answers since I have to give credit to this for changing my thinking. However, I think I'll stop doing that now. Sorry Jan :)

So the point is that we can change our implementation to the following:

public class Customer
{
    public CustomerEnabledEvent Enable()
    {
        _enabled = true;

        return new CustomerEnabledEvent(_id);
    }
}

Now our service can be changed to use an injected dispatcher:

public class CustomerService
{
    private IDomainEventDispatch _dispatcher;
    private ICustomerRepository _customerRepository;

    public CustomerService(ICustomerRepository customerRepository, IDomainEventDispatch dispatcher)
    {
        _customerRepository = customerRepository;
        _dispatcher = dispatcher;
    }

    public void Enable(Guid customerId)
    {
        _dispatcher.Raise(_customerRepository.Get(customerId).Enable());
    }
}

So no singleton is required and you can happily inject the dependency.

Madeleinemadelena answered 11/3, 2015 at 4:29 Comment(0)
P
2

I have never used a static DomainPublisher and I have other arguments than @Eben on how I handle it. This is just my personal experience and here are some of the reasons that I want to share :

  • because aggregate root produce events and the rule of thumb is that you should not inject anything inside your entitites, when you use injectable Domain Publisher you have to either introduce a domain service to invoke domain logic on the aggregate and raise an event with the Domain Publisher, either you handle it like explained by @Eben in the application service or you break the rule of thumb and you make your entitites injectable.
  • if you chose to use a domain services when not needed, just for injecting a domain publisher makes your code more cluttered. The business intent is less obvious and it adds more objects to manage and their dependencies than needed.

What I do, is to have a collection of events to publish on the aggregate root entity. Each time an event should be published, it is just added to the collection. I inject the domain publisher into the aggregate root repository. Thus publishing of events can be handled at the infrastructure level by the domain publisher in the repository. Because a domain publisher implementation very often has to deal with middleware like queues and buses, infrastructure level is right place to handle it correctly IMO. You can more easily deal with strategies of how you deal with publishing events when for example there is an exception when saving your entity to the database. What you would not want is to publish events and not save entitites in your database or the other way round.

Panhellenism answered 11/3, 2015 at 13:26 Comment(3)
Using a 'double-dispatch' approach is also OK but would imply that the dispatcher interface is in the domain. Since domain events are, well, domain artifacts it means the dispatcher could also be defined there. It does, however, mean that a specific infrastructure implementation would probably be required. One wouldn't necessarily need a domain service as there is always going to be some layer interacting with the domain. I do think returning event will simplify testing.Madeleinemadelena
With regards to the contained list of events my humble opinion is mentioned here: #28719519 :)Madeleinemadelena
You don't need a Domain Dispatcher in your domain. Each aggregate root has only a collection of events. This is handled after, in infrastructure (repository implementation)Antiknock
H
1

If you create class EventHandlerFactory with generic method Create<T> and T is constrained by IEventHandler<T> type, then this class will not service locator, but factory, because you create only IEventHandler<T> instances. At the same time Service locator is like God Object, he know everything.

More about this here

Houdan answered 10/3, 2015 at 16:55 Comment(4)
Wouldn't a Factory be overkill here ? You don't necessarily want to instantiate a brand new event handler each time an event pops up. I mostly use Factory when the consumer object needs to control the entire lifecycle of its dependency -- newing it up and disposing it when it wants (typically, in a using() clause).Candlestand
@Candlestand Controlling lifecycle it's a implementation detail. This detail you can hide behind a factory like a Object pool. You can combine abstract factory and object pool together, some event handlers you can create each time, another handler you can pops up from object pool, but it is important to incapsulate this details behind a factory. And as Eben Roux said, inject EventDispatcher into your service, there is no need to keep the Raise logic in static class.Houdan
I disagree. While a DI container might indeed have something like Abstract factory + object pool internally, if we use a DI container we don't need to know about these concepts in our dependency graph wireup code (Composition Root), let alone at consumer object level at the far end of the graph.Candlestand
@Candlestand Composition Root via DI container, hand coded Composition Root - it's all the same. It depends on the tasks. If client controls a lifecycle of created object, then factory will just create new instance. But in current situation, as I understood, we just fire and forget events, and client doesn't know, who receives event and how this event would be processed.Houdan

© 2022 - 2024 — McMap. All rights reserved.