Zdecydowałem się kontynuować poprzedni wątek, gdyż na ten temat jest jeszcze naprawdę dużo do powiedzenia. Tym razem chciałbym się skupić na tym, jak dostarczyć odpowiednie fragmenty kodu do pozostałych członków zespołu tak, by każdy mógł go używać w odpowiedni sposób. Zwrócimy uwagę na odpowiednie zarządzenie HttpClient’em, polityką powtarzania zapytań, jak i przybliżymy temat circuit breaker’a. Nie pozostaje mi nic innego niż zaprosić Was do kolejnego wpisu w Cesarstwie-Dev!

Dla kogo?

Ten artykuł zdecydowanie będzie stał na troszkę wyższym poziomie. Znajomość C# będzie zalecana, choć nie wymagana. Przykłady rzeczywiście będą napisane w języku C#, jednak wiedza tutaj przekazana jest uniwersalna, i warto go przeczytać już na początku swojej drogi programistycznej.

Słowniczek!
DI: Dependency Injection
Status odpowiedzi HTTP: Odpowiednie statusy określają stan odpowiedzi. Np. 200 to znaczy OK, a 404 – NotFound. Tutaj znajdziecie szerszą listę: link.

Wstęp

Pamiętacie interfejs, który przygotowaliśmy w poprzednim artykule technicznym? Jeśli nie czytaliście tego, w jaki sposób można podejść do integracji z zewnętrznym systemem za pomocą testów, to gorąco zachęcam od przeczytanie tego artykułu. Dla przypomnienia, naszym zadaniem jest integracja z API Narodowego Banku Polskiego i dostarczenie klienta API, który będzie w stanie zwrócić kurs dla odpowiedniej waluty z odpowiedniej tabeli. Interfejs, którego nazewnictwo pozostawia wiele do życzenia, wygląda następująco:

Na pewno przydałaby się wiedza domenowa, żeby być w stanie odpowiednio nazwać rodzaje tabel (A, B, C). Nie to jest jednak tematem artykułu, chociaż na pewno powstaną liczne wpisy związane z potrzebą posiadania wiedzy domenowej, współpracą z ekspertami dziedziny oraz podejściem DDD (domain-driven-design). Wracając jednak do tematu! Czy nie wystarczy napisać w dokumentacji, że istnieje interfejs o zadanej nazwie, że napisaliśmy klasę implementującą ten interfejs? Każdy przecież może zarejestrować sobie to w odpowiednim miejscu i spokojnie używać? Przybliżmy zatem implementację naszego interfejsu. Przed tym jednak ponownie dodam, że jestem świadomy niedoskonałości tego kodu. Wszelkie uproszczenia są stosowane w celu zapewnienia maksymalnego skupienia na temacie rozważań.

Rejestracja w mechanizmie DI

Jak może wyglądać rejestracja takiej klasy?

Na przykład tak jak w pierwszej metodzie, albo drugiej. Jest naprawdę sporo opcji w jaki można zarejestrować takiego klienta. A czy którakolwiek z powyższych opcji jest poprawna? Nie! Musimy więc zapewnić, że każdy kto będzie korzystać z naszego klienta będzie robić to w odpowiedni sposób. Oczywiście, warto w odpowiednim miejscu w dokumentacji napisać jak powinno się używać takiego klienta, najlepiej z przykładami. Skupmy się jednak na tym by maksymalnie ułatwić innym używanie naszego narzędzia. Od czego zaczniemy? Zmieńmy klasę implementującą nasz interfejs z publicznej na wewnętrzną (public -> internal), dzięki temu nikt nie dostanie się do tej klasy w innym projekcie niż obecny (w idealnym świecie, byłby to projekt skupiny na infrastrukturze). Następnie przygotujmy odpowiednią metodę za pomocą której inni będą mogli zarejestrować naszego klienta w poprawny sposób.

Czym się różni ta rejestracja od poprzednich? Wykorzystuje wbudowany mechanizm do dystrybuowania HttpClient’a pomiędzy instancjami. Dodatkowo, wymuszamy na pozostałych członkach zespoły korzystanie z tej metody, gdyż nie mogą bezpośrednio dostać się do implementacji naszego interfejsu. Po więcej ciekawych informacji i porad kieruję do arcyinteresującego artykułu. Osobiście najczęściej z wersji szóstej z tego artkułu. Ale myślę ze już dość na temat rejestracji! Przejdźmy do kolejnego etapu!

Retry policy

Nigdy nie możemy w stu procentach ufać zewnętrznym systemom. Ba, nigdy nie możemy ufać nawet nam samym. Czy powinniśmy zwracać błąd za każdym razem gdy zewnętrzny system zwróci niepoprawną odpowiedź? Odpowiedź jest banalna! To zależy. Czasami zależy nam przede wszystkim na czasie odpowiedzi, a niejednokrotnie wolimy się skupić na tym, aby otrzymać aktualne dane. Wybór powinien być po stronie użytkownika naszego klienta! W jaki sposób osiągnąć taki efekt? W celu użycia mechanizmu retry policy skorzystamy z popularnej biblioteki Polly. Wpierw jednak zezwólmy użytkownikom na korzystanie z opcji, jakie ofertuje nam nam HttpClientBuilder.

W jaki sposób klient może wykorzystać tego budowniczego w celu użycia Retry Policy?

Powyższa rejestracja w zupełności wystarczy, by nasz klient obsługiwał ponawianie żądań po spełnieniu odpowiednich warunków. Mechanizm ten ma wiele różnych możliwości, miedzy innymi powtarzanie przy odpowiednim statusie odpowiedzi, bądź zawartości tej odpowiedzi. Powyższy kod powtarza zapytania o statusach 500+ (server errors) oraz 408 (request timeout). W jaki sposób je powtarza? Tu również biblioteka daje nam wiele opcji. Ja najczęściej korzystam z powyższej konfiguracji, która, w razie błędu, powtarza zapytanie maksymalnie 3 razy, czekając kolejno 2, 4 i 8 sekund. Nic prostszego, prawda?

Circuit breaker – teoria

Nie każdy zna powyższy termin, więc zacznę od odrobiny teorii. Wzorzec Circuit Breaker działa na zasadzie obwodu elektrycznego. Gdy obwód jest zamknięty – prąd płynie. Gdy obwód jest otwarty – prądu brak. Tak samo działa to w programowaniu – gdy obwód jest zamknięty, to nasze zapytania trafiają do zewnętrznego systemu i próbujemy je przetworzyć. Gdy odwód jest otwarty – nasze zapytania zwracają od razu błąd (bądź pewną ustaloną przez nas odpowiedź). W programowaniu występuje jeszcze jeden stan: pół-otwarty. Zobaczmy zatem wykres!

W jakim celu wprowadzono stan półotwarty? W tym stanie tylko limitowana liczba żądań trafia do zewnętrznego systemu. Zapobiega to nagłemu zalaniu żądaniami serwisu, który powinien mieć czas teraz na regenerację. Postaram się teraz opisać flow, jakie towarzyszy temu procesowi, by utrwalić tę ważną wiedzę.

  1. Mamy działający serwis, który co pewien czas zwraca pojedyncze błędy – obwód zamknięty
  2. Serwis zaczyna zwracać błędy, po otrzymaniu ustalonej liczby błędów obwód się otwiera – obwód otwarty
  3. Obecnie każde żądanie skierowanego do naszego serwisu kończy się prawie natychmiastową odpowiedzią (o błędzie, bądź z odpowiednimi danymi) – obwód otwarty
  4. Co pewien ustalony czas limitowana liczba żądań trafia do serwisu zewnętrznego – obwód półotwarty
  5. Lecz żądania te kończą się błędem – obwód otwarty
  6. Obwód ponownie wchodzi w stan próbowania – obwód półotwarty
  7. Odpowiednia liczba żądań zakończyła się sukcesem – obwód zamknięty

Mam nadzieję, że teraz idea działania circut breaker’a jest jasna. Korzystanie z tego mechanizmu wiąże się z wieloma plusami. Wśród nich można wymienić bycie zgodnym z podejściem failFast oraz nieobciążanie serwisu, który powinien się powoli regenerować, nadmiarowymi żądaniami.

Circuit breaker – praktyka

Przejdźmy jednak do implementacji! W jaki sposób dodać Circuit Breaker’a do naszego klienta API NBP? Za pomocą znanej już nam biblioteki Polly!

Powyższy kod przedstawia sposób dodania kolejnego PolicyHandler’a do naszego budowniczego HttpClient’a, jak i dwie przykładowe polityki Circuit Breaker’a. Dolna przedstawia proste, lecz często wystarczające, użycie tego mechanizmu. Po dwóch błędach z rzędu obwód zostanie otwarty przez 30 sekund. Wyższa polityka przedstawia bardziej zaawansowany scenariusz. Zapnijmy pasy! Ten przykład otworzy obwód wtedy, gdy 10% żądań w ciągu 60 sekund zostanie zakończonych błędami, przy czym w trakcie tego czasu musi również wystąpić przynajmniej 10 żądań. Po spełnieniu powyższych warunków obwód zostanie otwarty na okres 30 sekund. To wszystko jest bardzo łatwe w użyciu i pięknie działa! Wystarczy tylko świadomość istnienia tych mechanizmów, by w znaczącym stopniu poprawić nasz software. Czy to nie wspaniałe?

Rate limiting

Zostaje jeszcze jedna ważna kwestia, jeśli chodzi o dostarczenie klient API. Poniżej wspaniały tweet na ten temat.

W tym wpisie jednak chciałem wyłącznie nakreślić istnienie takiego problemu. Nie chciałbym skupiać się na jego rozwiązaniu, gdyż jest to temat bardzo obszerny. Na pewno w przyszłości pojawi się wpis na ten temat! Warto jednak o tym pamiętać przy własnym kodzie. Mogę również wspomnieć że jednym z rozwiązań jest… użycie Polly! Wystarczy że klikniecie tutaj, a wszystko stanie się jasne.

Podsumowanie

Dzisiejszy wpis pokazał nam wiele ciekawych mechanik, które możemy w prosty sposób dodać do naszego oprogramowania. W krótkim czasie zaczerpnęliśmy sporej dawki wiedzy, która może ułatwić nam, jak i innym, korzystanie z klienta API do zewnętrznego systemu. Oczywiście zewnętrznym systemem może być też mikroserwis napisanym przez nas samych. Implementując te mechanizmy warto zamieścić w dokumentacji naszego serwisu jakie możliwości ofertuje taki, a nie inny sposób jego rejestrowania. Niech każdy wie co może zyskać dzięki kilku dodatkowym linijkom w kodzie rejestracji klienta.

Kolejna porcja technicznego mięska za nami, mam nadzieję że Wam też się podobało! Kolejny wpis już będzie na inny temat niż integracje, chociaż mówię to z pewnym smutkiem, gdyż zdecydowanie jest to mój ulubiony rodzaj zadań. Jeśli dopiero trafiliście tutaj, to polecam Wam sprawdzić pozostałe artykuły w Cesarstwie-Dev, w szczególności pierwszy post, który opisuje początek całego projektu. Wpis ten znajdziesz klikając tutaj!

A tymczasem życzę Wam miłego dnia i serdecznie pozdrawiam!


2 Komentarze

Używaj języka biznesu! - Cesarstwo Dev · 2020-10-11 o 19:22

[…] o podobnej tematyce. Jeśli odczuwacie ochotę przeczytania kolejnego wpisu, to co powiecie na temat odpowiedniego dostarczania klienta API? Nie wykluczam, że bardziej zainteresuje Was przetwarzanie bezserwerowe na platformie Azure. […]

dotnetomaniak.pl · 2020-09-18 o 15:35

Jak dostarczyć klienta API

Dziękujemy za dodanie artykułu – Trackback z dotnetomaniak.pl

Dodaj komentarz

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