4

我在这里提出了一个问题:Raising Domain Events For Multiple Subscribers,答案让我想到了以下模式,我可以像这样拥有 IEventPublisher:

public interface IEventPublisher<T>
{
    void Publish(T data);
}

和这样的 IEventSubscriber :

public interface IEventSubscriber<T>
{
    void Handle(T data);
}

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

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

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

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

_genericPublisher.Publish(new ThingyChangedEvent { ThingyId = model.Id});

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

任何建议都非常感谢。

编辑:

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

public interface IEventManager
{
  void Publish<T>(T args) where T : IEvent;
}

公共类 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);
  }
}

}

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

_eventManager.Publish<ThingyChangedEvent>(new ThingyChangedEvent() { ThingyId = Guid.NewGuid() });

这是我不喜欢这个解决方案的地方:

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

 IEnumerable<IEventSubscribersProvider<IEvent>> 

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

Resolve<IEventSubscribersProvider<T>>.

第二个问题不是什么大问题,但如果能够调用发布而不必像这样传递类型,那就太好了:

_eventManager.Publish(new ThingyChangedEvent() { ThingyId = Guid.NewGuid() });

我想如果有人对如何解决这两个问题有任何建议,尤其是问题 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?
}

非常感谢您的任何建议。

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

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

我现在会这样做,但希望能找到一个更好的长期解决方案。

4

1 回答 1

5

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

您需要一个“基本”类型,它是您接受为“事件”的最小(或最窄)类型。我通常使用标记界面,例如public interface IEvent{}. 当然,您可以从Object; 但我发现使用标记接口来明确订阅或发布“事件”这一事实很有用(这意味着您不能只发布任何类型的对象,只能发布“事件”)。

从处理多类型方面来看,您需要编写一个类来进行缩小(采用派生类型并将相同的对象“发布”为派生类型)。即使这样,您也需要通过适当的较窄实例来路由您的事件(以“绕过”方差限制)。然后,当然,您可以有多个订阅者订阅相同的事件类型;所以你需要一些东西来将事件路由到多个订阅者。这通常称为多路复用器,它是IEventPublisher. 每种事件类型都需要一个多路复用器——这将使用较窄的。给定事件使用哪个多路复用器取决于类型,因此多路复用器的集合将由字典管理,因此您可以按类型查找它们。然后,要按类型将事件发布给多个订阅者,您只需查找多路复用器并调用其IEventPublisher.Publish方法即可。管理多路复用器的东西是一种类型,IEventPublisher通常称为调度程序(有些人可能称之为路由器)。

例如:

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

所以,你基本上发布了 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());

当然,事件将派生自 IEvent:

public class EventOne : IEvent
{
}

public class EventTwo : IEvent
{
}

此特定限制未考虑多次调度事件。例如,我可以拥有一个订阅者EventOne和一个订阅者IEventEventOne如果一个对象是通过调度程序发布的,这个实现只是发布给订阅者EventOne——它不会发布给IEvent订阅者。我将把它作为练习留给读者:)

更新:

如果您希望做的是自动连接订阅者而无需构建它们(我认为这没有多大价值,请考虑使用Composition Root),那么您可以向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);
    }
}

您将在其中使用如下:

var d = new Dispatcher<IEvent>();
d.InitializeSubscribers();

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

于 2013-03-29T01:17:54.137 回答