Jak automatycznie ponawiać operacja oraz cachować dane z interceptorami w Autofac?

Wprowadzenie

W aplikacji czasami mamy fragmenty kodu, które dodajemy w różnych miejscach. Tak jak ostatnio opisywałem, możemy chcieć dodać cachowanie danych na poziomie logiki biznesowej, aby zmniejszyć ilość 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 czemu 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 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ż z sobą sporo problemów. Po pierwsze taka magia może być 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 zadaniem będzie zapisywać w pliku logu informacji o wywołaniach poszczególnych metod. Przez 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 nuget (Autofac.Extras.DynamicProxy). W tym pakiecie znajduje się interfejs IInterceptor, który musimy zaimplementować, aby nasz 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 metody. 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, wtedy właściwa metoda nie zostanie wykonana. W kolejnym interceptorze wykorzystamy tą możliwość.

Aby wykorzystać interceptor w aplikacji musimy trochę zmodyfikować rejestrację typów w kontenerze. Poniżej jest pokazana rejestracja 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 email, w których pokaże Ci w jaki sposób pracować efektywnej i szybciej w Visual Studio. Poznasz dodatkich, 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 tych krokach 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 jest 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. Dodając nowe klasy do aplikacji będą one od razu miały dodane te zachowanie i to bez pisania za każdym razem tego samego kodu.

CacheInterceptor

Kolejnym interceptorem, który Ci przedstawie będzie interceptor, który dodaje do aplikacji w sposób deklaratywny cachowanie danych. W wpisie o CacheManagerze pokazałem jak cachować 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 cachowaniem. Przez co czasami programista potrafi zapomnieć dodać np. usuwanie danych z cache podczas aktualizacji danych przez co aplikacja wyświetla jeszcze stare dane. Dlatego warto dodać do aplikacji cachowanie z wykorzystaniem interceptora.

Poniżej znajduje się kod interceptora. Wykorzystuje on tą samą usługę, którą pokazałem Ci w wpisie o CacheManagerze. W przykładzie do tego wpisu wykorzystuje 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.

W pierwszej kolejności 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. W przypadku, gdy typ nie jest logiką biznesową, wtedy interceptor wywoła właściwą metodę i nie zmieni jej w żaden sposób.

W przypadku logiki biznesowej interceptor sprawdza pierw, 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ę) i 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 podobne do tego co było wcześniej, więc nie będą go już pokazywał w wpisie. Znajduje się on w przykładzie na githubie: https://github.com/danielplawgo/AutofacInterceptors.

Poniżej jest 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. Natomiast drugie 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że w tym wpisie jest PollInterceptor. Za jego pomocą dodamy użycie biblioteki Polly do wywołań operacji na bazie danych. Będziemy chcieli w sytuacji, gdy na przykład serwer bazy danych jest niedostępny ponowić operację lub gdy na przykład 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 tej sposób, że nastąpią trzy próby ponowienia operacji. Każdy z dodatkowym opóźnieniem. W przykładzie reguła jest trochę zdefiniowana pod testy, aby można by w miedzy czasie wyłączyć i włączyć serwer, aby sprawdzić, czy interceptor działa.

PollInterceptor dodawany jest do wywołań klas repozytorium:

Natomiast jego samo 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 konfiguracje w tym przypadku autofaca) dodać jakieś zachowanie 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 widocznie jawnie w kodzie. Trzeba wiedzieć, że są używane interceptory i gdzie się znajdują. Dodatkowo zawsze użycie interceptorów wiąże się z narzutem wydajnościowy. 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ć operacja oraz cachować dane z interceptorami w Autofac?

Dodaj komentarz

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