Nasza droga do Reactive Extensions – cz. 2 – Event Aggregator na ratunek

W poprzednim wpisie opisałem problem z wyborem architektury aplikacji przed jakim stanął nasz zespół. W drugiej części opiszę jakie problemy udało się rozwiązać dzięki wzorcowi event aggregator, jakie pozostały nie rozwiązane, a jakie powstały w wyniku jego użycia.

Wzorzec Event aggregator, zwany czasami mediatorem albo event busem, dostarcza kanału komunikacji pomiędzy modułami aplikacji, ograniczając powiązania między nimi: moduł wysyłający wiadomość nie ma pojęcia kto na niego zareaguje, nie wiem nawet czy ktokolwiek to zrobi. Moduł oczekujący na wiadomość zazwyczaj nie musi znać nadawcy, interesuje go tylko treść wysłanej wiadomości.

Modułem naszej aplikacji, w którym ten wzorzec jest szczególnie użyteczny, jest obszar powiadomień.

W aplikacji istnieje tylko jedna instancja klasy odpowiadającej za ten obszar, klasa ta nasłuchuje na zdarzenia wysyłane przez wiele różnych klas. Bez tego wzorca wyświetlenie powiadomienia o przyjściu nowej wiadomości wymagałoby śledzenia dostępności samego serwisu wiadomości, zmian na liście rozmów oraz zmian na liście wiadomości w każdej rozmowie. Co gorsza każdy moduł, który chciałby reagować na przyjście nowej wiadomości musiałby powtórzyć znaczną część tego kodu. Dzięki event-aggregatorowi ta część kodu jest napisana raz, w klasie która wysyła wiadomość, natomiast klasy zainteresowane przyjściem nowej wiadomości wywołują tylko jedną linijkę:

_messageAggregator.Register(MessageHandlingMethod);

Drugim problemem jakim rozwiązał ten wzorzec, jest komunikacja między wątkami. Napisaliśmy jedną metodę rozszerzającą do event-aggregatora, która przenosi obsługę powiadomienia na główny wątek aplikacji:

public static void RegisterOnDispatcher(
    this MessageAggregator self,
    DispatcherService dispatcherService,
    Action handler)
{
    self.Register(message => dispatcherService.Invoke(() =>f handler(message)));
}

Zrozumienie ciała metody wymaga chwili zastanowienia, jednak jej użycie jest proste:

_messageAggregator.RegisterOndispatcher(MessageHandlingMethod);

Problemy z event-aggregatorem

Wzorzec ten jest tak wygodny, że bardzo szybko zdominował znaczną część naszego kodu. W pewnym momencie zorientowaliśmy się jednak że powoduje on pewne problemy, a innych nie rozwiązuje.

Po pierwsze wiele klas wylicza swój stan początkowy na podstawie stanu serwisu, a na wiadomość zapisuje się żeby odświeżyć swój stan, co powoduje że każda z tych klas ma dwie zależności: serwis i event-aggregator.

Wysłanie wiadomości przez event-aggregatora wymaga napisania dodatkowej klasy, ponieważ programiści są z natury leniwi starają się pominąć ten krok gdy tylko jest to możliwe, nawet kosztem „wycieku abstrakcji”. Zdarzały się przypadki gdy mieliśmy serwis, który nasłuchiwał na zdarzenia z dwóch innych serwisów i wyliczał jakąś wartość, jednak nie wysyłał żadnego powiadomienia o zmianie tej wartości. Klasy które korzystały z tej klasy nasłuchiwały na zdarzenia z oryginalnych serwisów.

Nasza aplikacja pozwala na zmianę wielu ustawień „w locie”, czyli bez konieczności restartu aplikacji. W momencie zapisu ustawień „menadżer ustawień” wysyła wiadomość. Wiele klas jest na nią zarejestrowanych i wykonuje swoją logikę, nawet jeśli konkretne ustawienie nie uległo zmianie, aby ograniczyć ten efekt dodaliśmy do wiadomości dodatkową informację o tym, które wartości uległy zmianie, co jednak zmusza nas do dopisywania kilku dodatkowych linijek kodu za każdym razem gdy pojawia się nowe ustawienie.

W aplikacji pojawiły się problemy wydajnościowe związane z komunikacją między wątkami. Co prawda wywołanie metody Dispatcher.Invoke nie jest szczególnie ciężkie i nie należy się go bać, a już na pewno nie kosztem niepoprawnego działania programu, jednak wywoływanie jej kilkanaście tysięcy razy może przyprawić o zadyszkę nawet mocne komputery i spowodować „zawieszenie” się aplikacji. Wspomniana wcześniej metoda RegisterOnDispatcher powodowała, że „Handler” był uruchamiany na głównym wątku aplikacji, nawet w przypadkach gdy efektywnie nic nie robił np. pierwsza linijka obsługi zmiany ustawień zawierała sprawdzenie czy ustawienie, którego zmianą jest klasa jest zainteresowana, nie uległo zmianie. Trudniejszym do wyśledzenia przypadkiem był przypadek gdy ustawienie uległo zmianie, ale efektywna wartość wyliczona na jego podstawie pozostała bez zmian. W tej architekturze były to problemy bardzo trudne do naprawy.

W kolejnej części artykułu opiszę jak biblioteka Reactive Extensions rozwiązała nasze problemy.

Jedna odpowiedź do “Nasza droga do Reactive Extensions – cz. 2 – Event Aggregator na ratunek”

  1. Pingback: dotnetomaniak.pl

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *