没有服务定位器的域事件

时间:2015-03-10 16:43:28

标签: c# domain-driven-design ninject inversion-of-control

提供域事件的默认实现:

表示域事件的接口:

public interface IDomainEvent { }

表示通用域事件处理程序的接口:

public interface IEventHandler<T> where T : IDomainEvent

筹集新活动的中央接入点:

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);
        }
    }
}

耗竭与:

public class SaleCanceled : IDomainEvent
{
    private readonly Sale sale;

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

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

引发事件的服务:

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

          // creates an instance of SaleCanceled event

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

是否有其他方法可以在不使用服务位置反模式的情况下使用域事件?

3 个答案:

答案 0 :(得分:7)

我想在你的情况下,你真的不需要。使用依赖注入,您可以在服务中注入IDomainEventDispatcher实现。

我之所以认为像这样的单身人士已经成为主流,这是一些着名开发人员首先提出的实现之一,起初并不觉得太错误。另一个原因是可能需要从域内提出事件:

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

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

在某个阶段,Jan Kronquist发现了这篇文章:http://www.jayway.com/2013/06/20/dont-publish-domain-events-return-them/

这是我第三次将这个链接添加到我的答案中,因为我必须赞扬这一点以改变我的想法。但是,我想我现在就不再那样做了。对不起Jan:)

所以关键是我们可以将我们的实现改为:

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

        return new CustomerEnabledEvent(_id);
    }
}

现在我们的服务可以更改为使用注入的调度程序:

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());
    }
}

因此不需要单例,您可以愉快地注入依赖项。

答案 1 :(得分:2)

我从未使用过静态DomainPublisher,除了@Eben之外我还有其他参数来处理它。这只是我的个人经历,以下是我想分享的一些原因:

  • 因为聚合根产生事件而经验法则是你不应该在你的权利中注入任何东西,当你使用可注入的域发布者时,你必须引入一个域服务来调用聚合上的域逻辑并引发一个事件。在域名发布者中,您可以像应用服务中的@Eben一样处理它,或者您违反了经验法则,并且您可以使您的权利可注射。
  • 如果您在不需要时选择使用域名服务,只是为了注册域名发布商会使您的代码更加混乱。业务意图不那么明显,它增加了更多的管理对象及其依赖性。

我所做的是拥有要在聚合根实体上发布的事件集合。每次发布一个事件时,它都会被添加到集合中。我将域发布者注入聚合根存储库。因此,事件的发布可以由存储库中的域发布者在基础结构级别处理。由于域名发布者实施经常需要处理队列和总线等中间件,因此基础架构级别是正确处理IMO的正确位置。您可以更轻松地处理有关如何处理发布事件的策略,例如将实体保存到数据库时会出现异常。你不想要的是发布事件而不是保存数据库中的权利,反之亦然。

答案 2 :(得分:1)

如果使用泛型方法EventHandlerFactory创建类Create<T>并且T受IEventHandler<T>类型约束,则此类不会为locator提供服务,而是为factory提供服务,因为您只创建{{1实例。同时服务定位器就像God Object,他知道一切。

有关此here

的更多信息