Jak automatycznie ponawiać operacje oraz cache’ować dane z interceptorami w Autofac?

Wprowadzenie

W aplikacji mamy czasami fragmenty kodu, które dodajemy w różnych miejscach. Tak jak ostatnio opisywałem, możemy chcieć dodać cache’owanie danych na poziomie logiki biznesowej, aby zmniejszyć liczbę zapytań do bazy. W przykładzie dotyczącym CacheManagera wywołanie usługi CacheService dodałem bezpośrednio w kodzie logiki. Z jednej strony tego kodu nie ma zbyt dużo, ale z drugiej strony fajnie byłoby mieć ten kod automatycznie w każdej logice biznesowej. Szczególnie że wcześniej czy później ktoś zapomni go dodać.

Podobna sytuacja ma miejsce w przypadku ponawiania operacji. Biblioteka Polly, którą opisywałem ostatnio, jest bardzo fajna i warto, aby automatycznie ponawiała operacje na bazie w momencie, gdy serwer nie odpowiada. Również tego kodu nie chcielibyśmy pisać za każdym razem.

Problem ten możemy rozwiązać na kilka sposób. Możemy użyć na przykład szablonów T4, które wygenerują nam cały potrzebny kod. Możemy również użyć tytułowych interceptorów, które umożliwią dodanie kodu w sposób dynamiczny i automatyczny do wszystkich klas, które tego potrzebują. Zobacz, jak to zrobić w Autofacu.

Interceptor

Interceptor jest fragmentem kodu, który jest wykonywany zamiast właściwej metody, która została wywołana. Dzięki temu możemy kontrolować jej wykonywanie w sposób deklaratywny bez zmiany już istniejącego kodu. Dzięki interceptorowi możemy wykonać jakąś logikę przed wywołaniem właściwej metody lub po jej wywołaniu. Możemy nawet zablokować jej wywołanie lub wywołać metodę ponownie w momencie wystąpienia błędu.

Dzięki interceptorom możemy dodać jakąś logikę wręcz do wszystkich metod w aplikacji. Jest to fajne, ale niesie też za sobą sporo problemów. Taka magia może być przede wszystkim niezrozumiała dla nowych osób w zespole, ponieważ nie widać jej bezpośrednio w kodzie. Musimy również pamiętać, że taki dynamiczny element zawsze ma też swój narzut wydajnościowy, który czasami może być istotny.

Zobacz na kilku przykładach, jak dodać do aplikacji interceptory z wykorzystaniem kontenera Autofac.

LoggerInterceptor

Pierwszym interceptorem, jaki dodamy do testowej aplikacji, będzie LoggerInterceptor. Jego zadanie polegać będzie na zapisywaniu w pliku logu informacji o wywołaniach poszczególnych metod. Przed wywołaniem metody zapiszemy jej nazwę w logu, podobnie będzie również po wywołaniu. Ten interceptor pomoże nam później w analizie kolejnych interceptorów.

Aby Autofac wspierał używanie interceptorów w aplikacji, musimy zainstalować dodatkowy pakiet z nugeta (Autofac.Extras.DynamicProxy). W tym pakiecie znajduje się interfejs IInterceptor, który musimy zaimplementować, aby nasza klasa była interceptorem. Interfejs definiuje jedną metodę, która będzie wywoływana zamiast właściwej implementacji. Poniżej logowanie wywoływania metod przez LoggerInterceptor:

Jak widzisz, nie ma tutaj jakiegoś skomplikowanego kodu. Za pomocą nLoga zapisujemy informacje o wywołaniach. Parametr invocation metody udostępnia nam informacje o właściwej metodzie. Wiemy, jak się ona nazywa oraz z jakiego typu pochodzi.

Kluczowym elementem interceptora jest wywołanie metody Proceed, która tak naprawdę wykonuje właściwą metodę. Gdy jej nie wywołamy,  właściwa metoda nie zostanie wykonana. W kolejnym interceptorze wykorzystamy tę możliwość.

Aby wykorzystać interceptor w aplikacji, musimy nieco zmodyfikować rejestrację typów w kontenerze. Poniżej przedstawiłem rejestrację klas logiki biznesowej z dodaniem interceptora do wywołań metod:

Aby skorzystać z interceptora, musimy zrobić dwie rzeczy podczas rejestracji: wywołać metodę EnableInterfaceInterceptors oraz za pomocą metody InterceptedBy przekazać typ interceptora.

Dodatkowo trzeba również zarejestrować sam interceptor w kontenerze:

Darmowy kurs Visual Studio

Pracując z setkami programistów, zauważyłem, że większość osób nie pracuje efektywnie w Visual Studio. W skrajnych przypadkach korzystali z kopiowania z wykorzystaniem menu Edit. Wiem, że to dziwne, ale naprawdę niektórzy tak pracują. Dlatego postanowiłem stworzyć kurs Visual Studio – aby pomóc koleżankom i kolegom w efektywniejszej pracy.

Przygotowałem 20 lekcji e-mail, w których pokażę Ci, w jaki sposób pracować efektywniej i szybciej w Visual Studio. Poznasz dodatki, bez których nie wyobrażam sobie pracy w tym IDE.

Po więcej informacji zapraszam na dedykowaną stronę kursu: Darmowy Kurs Visual Studio.

Quiz C#

Ostatnio przygotowałem również quiz C#, w którym możesz sprawdzić swoją wiedzę. Podejmiesz wyzwanie?

LoggerInterceptor – działanie

Po wykonaniu powyższych kroków interceptor będzie już przechwytywał wywołania logiki biznesowej. Przykład na githubie ma również użyty ten interceptor w klasach repozytorium. Poniżej znajduje się fragment pliku logu, który zawiera wynik działania interceptora dla akcji Index kontrolera ProductsController, która wywołuje metodę GetAllActive logiki biznesowej i później niżej metodę GetAllActive z repozytorium:

Jak widać, interceptor działa i zapisuje informacje o wywołaniach. Co fajne, dodanie interceptora w ten sposób spowoduje, że będzie on działał dla wszystkich wywołań metody z logiki biznesowej. Kiedy dodamy nowe klasy do aplikacji, będą one od razu miały dodane to zachowanie – i to bez pisania za każdym razem tego samego kodu.

CacheInterceptor

Kolejnym interceptorem, który Ci przedstawię, będzie interceptor, który dodaje do aplikacji w sposób deklaratywny cache’owanie danych. We wpisie o CacheManagerze pokazałem, jak cache’ować dane, pisząc kod bezpośrednio w klasie logiki biznesowej. Tamto podejście miało ten problem, że trzeba pamiętać o kodzie związanym z cache’owaniem. Przez to czasami programista potrafi zapomnieć dodać np. usuwanie danych z cache podczas aktualizacji danych, co w efekcie powoduje wyświetlanie przez aplikację starych danych. Dlatego warto dodać do aplikacji cache’owanie z wykorzystaniem interceptora.

Poniżej znajduje się kod interceptora. Wykorzystuje on tę samą usługę, którą pokazałem Ci we wpisie o CacheManagerze. W przykładzie do tego wpisu wykorzystuję tylko cache w pamięci procesu.

Ten interceptor jest już dużo bardziej rozbudowany niż poprzedni. Jego działanie jest podzielone na kilka kroków.

Na początku sprawdzamy, czy typ, na którym jest on wywołany, jest faktycznie logiką biznesową. W rejestracji w autofac powinien być on podpięty tylko pod nią, ale warto dodać takie zabezpieczenie. Kiedy typ nie jest logiką biznesową, interceptor wywoła właściwą metodę i nie zmieni jej w żaden sposób.

W przypadku logiki biznesowej interceptor sprawdza najpierw, czy następuje wywołanie metody GetById. Jeśli tak, to w pierwszej kolejności sprawdza, czy w cache są już zapisane dane. Gdy się tam znajdują, to wtedy interceptor zwraca te dane z metody (ustawienie właściwości ReturnValue parametru invocation). W takiej sytuacji właściwa metoda nie zostanie wywołana. W przeciwnym wypadku metoda się wykonuje i wynik jest zapisywany w cache.

Dla pozostałych metod z logiki biznesowej interceptor sprawdza, czy należy usunąć dane z cache (wywołana metoda ma odpowiednią nazwę), a jeśli tak, to usuwa dane.

CacheInterceptor – działanie

Podobnie jak wcześniej, aby interceptor zadziałał, należy go zarejestrować w kontenerze oraz dodać jego wywołanie do rejestracji klas logiki biznesowej. Kod jest bardzo podobny do tego, co było wcześniej, więc nie będę go już pokazywał we wpisie. Znajduje się on w przykładzie na githubie: https://github.com/danielplawgo/AutofacInterceptors.

Poniżej znajduje się log z działania tego interceptora. W pierwszej kolejności wyświetliłem dwa razy widok edycji produkt oraz później zapisałem dane.

Pierwsze wyświetlenie widoku wywołało metodę z repozytorium (widać, że CacheManager nie znalazł danych w cache) i później dodało dane do cache. Drugie natomiast znalazło już dane w cache, więc metoda z repozytorium nie została pobrana.

Podczas zapisu danych widać, że na końcu CacheManager usuwa dane z cache, aby kolejne pobranie nastąpiło już z repozytorium.

PollyInterceptor

Ostatnim interceptorem, który Ci pokażę w tym wpisie, jest PollInterceptor. Za jego pomocą dodamy użycie biblioteki Polly do wywołań operacji na bazie danych. Będziemy chcieli ponowić operację na przykład w sytuacji, gdy na przykład serwer bazy danych jest niedostępny lub gdy dwie transakcje się zablokują i jedną z nich zakończy z błędem serwer bazy.

Sam interceptor wygląda prosto:

Reguła w Polly jest zdefiniowana w ten sposób, że nastąpią trzy próby ponowienia operacji. Każda z dodatkowym opóźnieniem. W przykładzie reguła jest nieco zdefiniowana pod testy, aby można w międzyczasie wyłączyć i włączyć serwer, aby sprawdzić, czy interceptor działa.

PollInterceptor dodawany jest do wywołań klas repozytorium:

Natomiast samo jego działanie wygląda tak (uruchomiłem aplikację z wyłączonym sql serwerem i po pierwszym błędzie uruchomiłem go):

Przykład

Na githubie znajduje się przykład do wpisu – https://github.com/danielplawgo/AutofacInterceptors. Aby go uruchomić, wystarczy w web.config ustawić poprawny connection string do bazy. W aplikacji zaimplementowany jest CRUD dla produktów (adres http://localhost:[port]/products).

Podsumowanie

Interceptor jest bardzo fajnym mechanizmem, który umożliwia nam w sposób deklaratywny (poprzez konfigurację w tym przypadku Autofaca) dodanie jakichś zachowań dla naszych klas. Dzięki temu nie musimy ręcznie w tych klasach dodawać tego kodu. Niestety ma to też swoje konsekwencje. Nowe osoby w zespole mogą mieć trochę problemów, ponieważ takie zachowanie nie jest widoczne jawnie w kodzie. Trzeba wiedzieć, że  interceptory są używane i wiedzieć, gdzie się znajdują. Dodatkowo użycie interceptorów zawsze wiąże się z narzutem wydajnościowym. Jakim? W jednym z kolejnych wpisów to sprawdzimy.

A jakie są Twoje doświadczenia z używaniem interceptorów? Używasz ich? Podoba Ci się ich idea?

1 thought on “Jak automatycznie ponawiać operacje oraz cache’ować dane z interceptorami w Autofac?

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *