środa, 11 sierpnia 2010

Event Aggregator - Cóż to takiego jest ?

Dziś chciałbym opisać kolejny wzorzec/mechanizm który będę używal i ma on na celu znaczne uproszczenie kodowania i eliminowanie niepotrzebnych powiązań miedzy obiektami.

Event Aggregator
Jak już wcześniej wspomniałem ten wzorzec/mechanizm ma na celu eliminowanie niepotrzebnych powiązań miedzy obiektami. Stanowi on pojedyncze źródło dla wielu obiektów. W najprostszej formie wygląda to tak że rejestrujemy klasy które dotyczą konkretnego zdarzenia do naszego event aggregatora. Te klasy nazywają się uchwytami(handlerami). Natomiast gdy wyślemy zdarzenie do event aggregatora on wybiera wszystkie uchwyty które danego zdarzenia dotyczą, posyła im to zdarzenie a one już zajmują się odbiorem tych zdarzeń i wykonywaniem akcji związanych z nim. W moim forum będę używał dosyć prostego rozwiązania ponieważ nie potrzebuje niczego skomplikowanego. Zdarzenie jest to zwykly obiekt ktory musi implementowac interfejs IEvent. np.
public class UserCreatedEvent : IEvent { }
Nasz event aggregator czyli główny ośrodek tez nie jest skomplikowana klasa:
public class EventAggregator : IEventAggregator
{
    private readonly SynchronizationContext _context;
    private readonly IListenerService _listenerService;

    public EventAggregator(SynchronizationContext context, IListenerService listenerService)
    {
        Check.Argument.IsNotNull(context, "context");
        Check.Argument.IsNotNull(listenerService, "listenerService");
            
        _context = context;
        _listenerService = listenerService;
    }
        
    public void SendMessage<T>(T message) where T : IEvent
    {
        var listeners = _listenerService.GetListeners<T>();

        SendAction(() => listeners.ForEach(x => SendMessageToListeners<T>(x, message)));
    }

    private void SendMessageToListeners<T>(IListenTo<T> x, T message) where T : IEvent
    {
        x.Handle(message);
    }

    private void SendAction(Action action)
    {
        _context.Send(state => action(), null);
    }
}
Sercem calego Event Aggregatora jest funkcja SendMessageToListeners. Ta funkcja odpowiada za rozsyłanie zdarzeń do odbiorcow. Wszystkie uchwyty(handlery) są rejestrowane prze kontener IoC. Natomiast ListenerService poprostu pobiera wszystkie te które dotyczą danego zdarzenia. Następnie za pomocą funkcji lambda Event aggregator już konkretnie wysyła zdarzenia do konkretnych uchwytów. Wszystko jest oczywiście wątkowo-bezpieczne(thread-safe). W razie gdyby miały być jakieś wywołania asynchroniczne. Strasznie to brzmi po polsku :P Event handler to nic innego jak klasa implementujaca intertejs IListenTo ktory wyglada tak:
public interface IListenTo<T> where T : IEvent
{
    void Handle(T message);
}
Jest to zwykla klasa z metoda Handle ktora ma w argumencie dany event.

A teraz praktyczny przykład. Powiedzmy ze tworzymy nowego użytkownika. Z samym utworzeniem wiąże się, nadanie mu podstawowych uprawnień, wysłanie maila potwierdzającego jego adres e-mail. W normalnym wypadku po prostu z danej metody tworzącej wywołalibyśmy metody z innych klas związane z tymi czynnościami. Czyli:
public void CreateUser()
{
    //tutaj akcje związane z dodawaniem użytkownika
    ...

    _rolesService.AddRole();
    _emailService.SendEmail();
}
Powstają niepotrzebne powiązania z rożnymi obiektami. W razie jakiś modyfikacji usług związanych z rolami czy wysyłaniem maili możliwe jest ze będzie potrzeba zmian w wielu miejscach w kodzie. W małych systemach to nie stanowi problemu natomiast w miarę powiększana się utrudnia prace, modernizacje danej aplikacji. To samo zadanie rozwiązane z pomocą naszego Event Handlera. Mamy zdarzenie CreatedUserEvent.
//tutaj mamy oddzielne klasy nie zwiazane w ogole z metoda CreateUser
class VerificationEmailHandler: IListenTo<CreatedUserEvent> { };
class BasicRoleHandler: IListenTo<CreatedUserEvent> { };
//--------------------------------------------------------
public void CreateUser()
{
    //tutaj akcje związane z dodawaniem użytkownika
    ...

    CreatedUserEvent event = new CreatedUserEvent();
    _eventAggregator.SendMessage(testEvent);
}
Odrazu widac ze usunęliśmy zbędne powiązania miedzy obiektami. Metoda CreatedUser nie ma żadnego pojęcia na temat innych usług(servicow) niezwiązanych konkretnie z nią. Poza tym łatwo taki kod rozszerzać o nowe uchwyty w miarę potrzeb gdyż wystarczy zarejestrować nowy uchwyt i zostanie on wykonany. Nie potrzeba żadnej zmiany w kodzie.

Oprócz samego Event Handlera dodałem oczywiście test związany z nim. Co za tym idzie stworzyłem nowy projekt testowy związany z mForum.Core. To tyle na dziś :)

2 komentarze:

Unknown pisze...

"Nasz event aggregator czyli glowna ośrodek tez nie jest skomplikowana klasa:" chyba wymaga przeredagowania :)

Krzysztof Hrabia pisze...

Masz racje dzięki :)

Prześlij komentarz