2013-03-28 25 views
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}); 

我無法弄清楚如何做雖然這是我可以」 t傳遞一個IEventPublisher的集合,而不像本例中那樣將T定義爲ThingyChangedEvent,而我想要的是根據傳遞給通用發佈者的類型來確定發佈者。

任何建議非常感謝。

編輯:

使用下面的答案,從這裏的一些信息OK: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); 
    } 
} 

}

我現在可以採取IEventManager eventmanager進行的實例由autofac解決一個構造函數和調用它,如下所示:

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

這裏是什麼,我不喜歡這個解決方案:

我不想在構造函數中使用ILifetimeScope的實例,我希望能夠獲取IEventSubscribersProvider的集合,但autofac不會reso射血,如果我問說:

IEnumerable<IEventSubscribersProvider<IEvent>> 

我只能解決它,如果我通過類型來發布,並呼籲:

Resolve<IEventSubscribersProvider<T>>. 

第二個問題是不是一個巨大的交易,但將是很好要能夠調用發佈,而不必通過類型以及像這樣:

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

我想,如果任何人有,因爲我不喜歡把一個如何解決這兩個問題,特別是第1期的任何建議對Au的依賴tofac在不同的項目。我唯一能想出的是一種經理類,它明確地採取我需要的如下:

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

非常感謝您的任何建議。

EDIT 2

大量的研究後,看着這個Autofac Generic Service resolution at runtime我不知道我能做到我想做的。我能拿出最好的解決辦法是這樣的:

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的依賴。

我現在就去,但希望能在長期內找到更好的解決方案。

+0

您正在介紹「訂閱者提供者」和其他一些「經理」類(可怕的術語,我的寵物之一)的概念來管理訂閱者提供者*和*「事件管理器」以避免容器或使用autofac。這些聽起來就像NarrowingSubscriber,Multiplexor和我詳細介紹的調度程序 - 除了我詳細介紹的作品和你正在變得非常複雜。其他類中的容器是Service Locator,我認爲這是一種反模式(http://lynk.at/eE0hik),懷疑Udi會這樣做。 –

回答

5

當你必須處理多種類型的事件時,它會變得有點複雜。正如你可能已經注意到你不能只使用派生的泛型類型,並期望像使用基類一樣使用它--.NET差異不支持你想使用它的地方。

您需要一個「基本」類型,它是您接受爲「事件」的最小(或最窄)類型。我通常使用marker interface,比如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)); 
    } 
} 

所以,你基本上發佈的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,訂購IEvent。如果EventOne對象通過調度程序發佈,則此實現僅向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>

+0

非常感謝您的回覆和所花費的時間,但我恐怕這不是我所追求的,儘管我可能會誤解。我們使用Autofac來自動加載任何IEventSubscribers,我們希望我們不必在任何地方調用Subscribe,並且我們可以簡單地在通用發佈服務器上調用Publish,以獲得該類型的任何IEventSubscribers,然後在每個發佈服務器上調用Handle。不幸的是,我們似乎無法讓Autofac使用泛型IEvent來解決這些問題。 – user351711

+0

查看自動加載用戶的更新。 –

+1

經過大量的玩耍之後,我已經走了與您所建議的幾乎完全相同的東西,非常感謝您對此的幫助。它花了一段時間,但它是有道理的:) – user351711