4

ここに質問を置きます:複数のサブスクライバーに対してドメイン イベントを発生させると、その答えにより、次のような 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});

この例のように T を ThingyChangedEvent として定義せずに IEventPublisher のコレクションを渡すことはできないため、これを行う方法を理解できませんが、汎用パブリッシャーに渡されるタイプに基づいてパブリッシャーを決定する必要があります。 .

どんな提案でも大歓迎です。

編集:

以下の回答とここからの情報を使用してOK :http://www.udidahan.com/2009/06/14/domain-events-salvation/

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

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

}

これで、autofac によって解決されたコンストラクターで IEventManager eventManager のインスタンスを取得し、次のように呼び出すことができます。

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

このソリューションについて私が気に入らない点は次のとおりです。

コンストラクターで ILifetimeScope のインスタンスを取得したくありません。IEventSubscribersProvider のコレクションを取得できるようにしたいのですが、次のように要求すると、autofac はこれを解決しません。

 IEnumerable<IEventSubscribersProvider<IEvent>> 

タイプをパブリッシュに渡して呼び出す場合にのみ解決できます。

Resolve<IEventSubscribersProvider<T>>.

2 番目の問題は大した問題ではありませんが、次のように型を渡さずに publish を呼び出せると便利です。

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

これら 2 つの問題、特に問題 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 Generic Service の解像度を調べた後、私が望むことを達成できるかどうかはわかりません。私が思いつく最善の解決策はこれです:

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. イベント タイプごとに 1 つのマルチプレクサが必要です。これは、狭い方を使用します。特定のイベントに使用するマルチプレクサはタイプに依存するため、マルチプレクサのコレクションはディクショナリによって管理されるため、タイプ別に検索できます。次に、タイプごとに複数のサブスクライバーにイベントを発行するには、単純にマルチプレクサーを検索してその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 回公開しています。ディスパッチャに 1 回、マルチプレクサに 1 回、より狭いサブスクライバに 1 回、非インフラストラクチャ サブスクライバに 1 回。2 種類のイベント ( および ) をサブスクライブする 2EventOneEventSubscriber人のサブスクライバー (および) がある場合、ディスパッチャーを作成して、次のようにイベントを発行することができます。EventTwoEventSubscriberEventOneEventTwo

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
{
}

この特定の制限では、イベントを複数回ディスパッチすることは考慮されていません。たとえば、 のサブスクライバーと のサブスクライバーをEventOne1 つ持つことができますIEvent。この実装は、オブジェクトがディスパッチャーを介して発行されたEventOne場合にサブスクライバーに発行するだけで、サブスクライバーには発行しません。私はそれを読者のための演習として残しておきます:)EventOneIEvent

アップデート:

サブスクライバーを作成せずに自動配線することを望んでいる場合 (これにはあまり価値がありません。Composition RootDispatcherを検討してください)、 (または他の場所に、かなり単純なメソッドを追加できます。必要に応じて) 互換性のあるすべてのサブスクライバーをサブスクライブするには:

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 に答える