域事件模式单点到队列事件

use*_*711 4 c# generics domain-events

我在这里提出一个问题:为多个订阅者提升域事件,答案让我得到以下模式,我可以像这样拥有一个IEventPublisher:

public interface IEventPublisher<T>
{
    void Publish(T data);
}
Run Code Online (Sandbox Code Playgroud)

和像这样的IEventSubscriber:

public interface IEventSubscriber<T>
{
    void Handle(T data);
}
Run Code Online (Sandbox Code Playgroud)

这个问题是我需要将每个发布者的实例传递给构造函数,如下所示:

public Service(IEventPublisher<ThingyChangedEvent> publisherThingyChanged)
{
   // Set publisher to local variable
}

// then call this in a method
_publisherThingyChanged.Publish(new ThingyChangedEvent { ThingyId = model.Id});
Run Code Online (Sandbox Code Playgroud)

理想情况下,我希望能够拥有一个包含任何IEventPublishers的通用发布者,所以我可以调用类似的东西:

_genericPublisher.Publish(new ThingyChangedEvent { ThingyId = model.Id});
Run Code Online (Sandbox Code Playgroud)

我无法弄清楚如何做到这一点,因为我无法传递IEventPublisher的集合而没有像在ThingyChangedEvent中那样定义T,而我想要的是根据传递给通用发布者的类型来确定发布者.

任何建议非常感谢.

编辑:

好的,使用下面的答案和来自这里的一些信息:http://www.udidahan.com/2009/06/14/domain-events-salvation/我想出了以下内容,但它并不完全存在:

public interface IEventManager
{
  void Publish<T>(T args) where T : IEvent;
}
Run Code Online (Sandbox Code Playgroud)

public class EventManager:IEventManager {Autofac.ILifetimeScope _container;

public EventManager(Autofac.ILifetimeScope container)
{
  _container = container;
}

//Registers a callback for the given domain event
public void Publish<T>(T args) where T : IEvent
{
  var subscribersProvider = _container.Resolve<IEventSubscribersProvider<T>>();
  foreach (var item in subscribersProvider.GetSubscribersForEvent())
  {
    item.Handle(args);
  }
}
Run Code Online (Sandbox Code Playgroud)

}

我现在可以在autofac解析的构造函数中获取IEventManager eventManager的实例,并按如下方式调用它:

_eventManager.Publish<ThingyChangedEvent>(new ThingyChangedEvent() { ThingyId = Guid.NewGuid() });
Run Code Online (Sandbox Code Playgroud)

以下是我不喜欢这个解决方案:

我不想在构造函数中使用ILifetimeScope的实例,我希望能够获取IEventSubscribersProvider的集合,但如果我要求说,autofac将无法解决此问题:

 IEnumerable<IEventSubscribersProvider<IEvent>> 
Run Code Online (Sandbox Code Playgroud)

如果我将类型传递给Publish并调用,我只能解决它:

Resolve<IEventSubscribersProvider<T>>.
Run Code Online (Sandbox Code Playgroud)

第二个问题并不是一个大问题,但能够调用发布而不必像这样传递类型会很好:

_eventManager.Publish(new ThingyChangedEvent() { ThingyId = Guid.NewGuid() });
Run Code Online (Sandbox Code Playgroud)

我想如果有人对如何解决这两个问题有任何建议,特别是问题1,因为我不喜欢在不同的项目中依赖Autofac.我能想出的唯一一个是某种类型的管理器类,它明确地采用了我需要的内容,如下所示:

public SomeConstructor(
    IEventSubscribersProvider<ThingyChangedEvent> thingChangedSubscribeProviders,
    IEventSubscribersProvider<SomeOtherEvent> someOtherSubscribeProviders,
    etc....)
{
     // Maybe take the EventManager as well and add them to it somehow but would be
     // far easier to take a collection of these objects somehow?
}
Run Code Online (Sandbox Code Playgroud)

非常感谢任何建议.

编辑2

经过大量研究并在运行时查看此Autofac通用服务解析后,我不确定我能达到我想要的效果.我能想出的最佳解决方案是:

public interface IEventSubscribersProviderFactory : Amico.IDependency
{
  IEventSubscribersProvider<T> Resolve<T>() where T : IEvent;
}

public class EventSubscribersProviderFactory : IEventSubscribersProviderFactory
{ 
  Autofac.ILifetimeScope _container;

  public EventSubscribersProviderFactory(Autofac.ILifetimeScope container)
  {
    _container = container;
  }

  public IEventSubscribersProvider<T> Resolve<T>() where T : IEvent
  {
    return _container.Resolve<IEventSubscribersProvider<T>>();
  }
}
Run Code Online (Sandbox Code Playgroud)

然后让EventManager在构造函数中使用IEventSubscribersProviderFactory来从该项目中删除对Autofac的依赖.

我现在会继续这样做,但希望从长远来看能找到更好的解决方案.

Pet*_*hie 5

当你必须处理多种类型的事件时,它会变得有点复杂.正如您可能已经注意到,您不能只使用派生泛型类型并希望像基础泛型一样使用它--.NET方差不支持您想要使用它的地方.

您需要一个"基础"类型,它是您接受为"事件"的最小(或最窄)类型.我通常使用一个标记接口一样public interface IEvent{}.当然,你可以从中得到Object; 但我觉得有一个标记界面可以使您订阅或发布"事件"显而易见(并且意味着您不能只发布任何类型的对象,只是"事件").

从多种类型方面的处理,您需要编写一个类来进行缩小(获取派生类型并"发布"相同的对象强制转换为派生类型).即便如此,您还需要通过较窄的适当实例来路由您的事件(以"绕过"方差限制).然后,当然,您可以拥有同一事件类型的多个订阅者; 所以你需要一些东西来将事件路由到多个订阅者.这通常被称为多路复用器,它将是一个专门化的IEventPublisher.每个事件类型需要一个多路复用器 - 这将使用更窄的.用于给定事件的多路复用器取决于类型,因此多路复用器的集合将由字典管理,因此您可以按类型查找它们.然后,要按类型向多个订阅者发布事件,只需查找多路复用器并调用其IEventPublisher.Publish方法即可.管理多路复用器的东西是一种类型,IEventPublisher通常称为Dispatcher(有些人可能称之为路由器).

例如:

public class NarrowingSubscriber<TBase, TDerived> : IEventSubscriber<TBase>
    where TDerived : TBase
    where TBase : IEvent
{
    private IEventSubscriber<TDerived> inner;

    public NarrowingSubscriber(IEventSubscriber<TDerived> inner)
    {
        if (inner == null) throw new ArgumentNullException("inner");
        this.inner = inner;
    }

    public void AttachSubscriber(IEventSubscriber<TDerived> subscriber)
    {
        inner = subscriber;
    }

    public void Handle(TBase data)
    {
        inner.Handle((TDerived)data);
    }
}

public class Multiplexor<T> : IEventSubscriber<T> where T : IEvent
{
    private readonly List<IEventSubscriber<T>> subscribers =
        new List<IEventSubscriber<T>>();

    public void AttachSubscriber(IEventSubscriber<T> subscriber)
    {
        subscribers.Add(subscriber);
    }

    public void RemoveSubscriber(IEventSubscriber<T> subscriber)
    {
        subscribers.Remove(subscriber);
    }

    public void Handle(T data)
    {
        subscribers.ForEach(x => x.Handle(data));
    }
}

public class Dispatcher<TBase> : IEventPublisher<TBase> where TBase : IEvent
{
    private readonly Dictionary<Type, Multiplexor<TBase>> subscriptions =
        new Dictionary<Type, Multiplexor<TBase>>();

    public void Publish(TBase data)
    {
        Multiplexor<TBase> multiplexor;
        if (subscriptions.TryGetValue(data.GetType(), out multiplexor))
        {
            multiplexor.Handle(data);
        }
    }

    public void Subscribe<TEvent>(IEventSubscriber<TEvent> handler)
        where TEvent : TBase
    {
        Multiplexor<TBase> multiplexor;
        if (!subscriptions.TryGetValue(typeof(TEvent), out multiplexor))
        {
            multiplexor = new Multiplexor<TBase>();
            subscriptions.Add(typeof(TEvent), multiplexor);
        }
        multiplexor.AttachSubscriber(new NarrowingSubscriber<TBase, TEvent>(handler));
    }
}
Run Code Online (Sandbox Code Playgroud)

所以,你基本上发布了4次.一旦到达调度员,一次到多路复用器,一次到较窄,一次到非基础设施用户.如果您有两个订阅者(EventOneEventSubscriberEventTwoEventSubscriber)订阅了两种类型的事件(EventOneEventTwo),那么您可以创建一个调度程序并发布如下事件:

var d = new Dispatcher<IEvent>();
var eventTwoSubscriber = new EventTwoEventSubscriber();
d.Subscribe(eventTwoSubscriber);
var eventOneSubscriber = new EventOneEventSubscriber();
d.Subscribe(eventOneSubscriber);

d.Publish(new EventOne());
d.Publish(new EventTwo());
Run Code Online (Sandbox Code Playgroud)

当然,事件将来自IEvent:

public class EventOne : IEvent
{
}

public class EventTwo : IEvent
{
}
Run Code Online (Sandbox Code Playgroud)

此特定限制不会多次考虑调度事件.例如,我可以订阅订阅者EventOne和订阅者IEvent.EventOne如果EventOne通过调度程序发布对象,则此实现仅发布给订阅者- 它不会发布给IEvent订阅者.我会把它作为读者的练习:)

更新:

如果你希望做的是自动连接订阅者而不必构建它们(我没有看到很多价值,考虑一个组合根)然后你可以添加一个相当简单的方法Dispatcher(或其他地方,如果需要)订阅所有兼容订户:

public void InitializeSubscribers()
{
    foreach (object subscriber in
        from assembly in AppDomain.CurrentDomain.GetAssemblies()
        from type in assembly.GetTypes()
        where !type.IsAbstract && type.IsClass && !type.ContainsGenericParameters &&
              type.GetInterfaces().Any(t => t.IsGenericType &&
                                            t.GetGenericTypeDefinition() == typeof (IEventSubscriber<>))
        select type.GetConstructor(new Type[0])
        into constructorInfo
        where constructorInfo != null
        select constructorInfo.Invoke(new object[0]))
    {
        Subscribe((dynamic) subscriber);
    }
}
Run Code Online (Sandbox Code Playgroud)

你使用如下:

var d = new Dispatcher<IEvent>();
d.InitializeSubscribers();
Run Code Online (Sandbox Code Playgroud)

... Dispatcher<T>如果需要,使用容器来解析对象.