没有服务定位器的领域事件
Domain Events without Service Locator
给出域事件的默认实现:
表示域事件的接口:
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);
}
}
是否有另一种方法可以在不使用服务位置反模式的情况下使用领域事件?
如果您使用通用方法 Create<T>
创建 class EventHandlerFactory
并且 T 受 IEventHandler<T>
类型约束,则此 class 将不会服务定位器,但是工厂,因为您只创建 IEventHandler<T>
个实例。同时Service locator就像God Object,他无所不知
更多关于这个here
我想在你的情况下你真的不需要。使用依赖项注入,您可以将 IDomainEventDispatcher
实现注入到您的服务中。
之所以我认为像这样的单例已成为主流,它是一些著名开发人员提出的首批实现之一,起初并没有太大的错误。另一个原因是可能需要从域内引发事件:
public class Customer
{
public void Enable()
{
_enabled = true;
DomainEvents.Raise(new CustomerEnabledEvent(_id));
}
}
在某个阶段我遇到了 Jan Kronquist 的 post:http://www.jayway.com/2013/06/20/dont-publish-domain-events-return-them/
这是我第三次将 link 添加到我的答案中,因为我不得不承认它改变了我的想法。但是,我想我现在会停止这样做。对不起简 :)
关键是我们可以将实现更改为以下内容:
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());
}
}
因此不需要单例,您可以愉快地注入依赖项。
我从未使用过静态 DomainPublisher,除了@Eben 之外,我还有其他关于我如何处理它的论据。这只是我的个人经历,以下是我想分享的一些原因:
- 因为聚合根产生事件并且经验法则是你不应该在你的实体中注入任何东西,当你使用可注入域发布者时你必须引入域服务来调用聚合上的域逻辑并引发一个Domain Publisher 的事件,要么像@Eben 在应用程序服务中解释的那样处理它,要么打破经验法则,让你的实体可注入。
- 如果您选择在不需要时使用域服务,仅仅为了注入域发布者会使您的代码更加混乱。业务意图不太明显,它添加了比需要更多的对象来管理及其依赖性。
我所做的是收集要在聚合根实体上发布的事件。每次发布事件时,它只是添加到集合中。我将域发布者注入聚合根存储库。因此,事件的发布可以由存储库中的域发布者在基础设施级别处理。因为域发布者的实现经常需要处理队列和总线等中间件,所以在 IMO 中,基础架构级别是正确处理它的正确位置。例如,在将实体保存到数据库时出现异常时,您可以更轻松地处理如何处理发布事件的策略。您不希望发布事件而不是将实体保存在数据库中,反之亦然。
给出域事件的默认实现:
表示域事件的接口:
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);
}
}
是否有另一种方法可以在不使用服务位置反模式的情况下使用领域事件?
如果您使用通用方法 Create<T>
创建 class EventHandlerFactory
并且 T 受 IEventHandler<T>
类型约束,则此 class 将不会服务定位器,但是工厂,因为您只创建 IEventHandler<T>
个实例。同时Service locator就像God Object,他无所不知
更多关于这个here
我想在你的情况下你真的不需要。使用依赖项注入,您可以将 IDomainEventDispatcher
实现注入到您的服务中。
之所以我认为像这样的单例已成为主流,它是一些著名开发人员提出的首批实现之一,起初并没有太大的错误。另一个原因是可能需要从域内引发事件:
public class Customer
{
public void Enable()
{
_enabled = true;
DomainEvents.Raise(new CustomerEnabledEvent(_id));
}
}
在某个阶段我遇到了 Jan Kronquist 的 post:http://www.jayway.com/2013/06/20/dont-publish-domain-events-return-them/
这是我第三次将 link 添加到我的答案中,因为我不得不承认它改变了我的想法。但是,我想我现在会停止这样做。对不起简 :)
关键是我们可以将实现更改为以下内容:
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());
}
}
因此不需要单例,您可以愉快地注入依赖项。
我从未使用过静态 DomainPublisher,除了@Eben 之外,我还有其他关于我如何处理它的论据。这只是我的个人经历,以下是我想分享的一些原因:
- 因为聚合根产生事件并且经验法则是你不应该在你的实体中注入任何东西,当你使用可注入域发布者时你必须引入域服务来调用聚合上的域逻辑并引发一个Domain Publisher 的事件,要么像@Eben 在应用程序服务中解释的那样处理它,要么打破经验法则,让你的实体可注入。
- 如果您选择在不需要时使用域服务,仅仅为了注入域发布者会使您的代码更加混乱。业务意图不太明显,它添加了比需要更多的对象来管理及其依赖性。
我所做的是收集要在聚合根实体上发布的事件。每次发布事件时,它只是添加到集合中。我将域发布者注入聚合根存储库。因此,事件的发布可以由存储库中的域发布者在基础设施级别处理。因为域发布者的实现经常需要处理队列和总线等中间件,所以在 IMO 中,基础架构级别是正确处理它的正确位置。例如,在将实体保存到数据库时出现异常时,您可以更轻松地处理如何处理发布事件的策略。您不希望发布事件而不是将实体保存在数据库中,反之亦然。