Ewolucja w projektowaniu i wytwarzaniu aplikacji internetowych przeszła przez kilka etapów. W latach dziewięćdziesiątych ubiegłego wieku mieliśmy „Spaghetti-oriented Architecture”, która charakteryzowała się długimi jak makaron metodami, pełnymi ifów i zależności. Można było znaleźć dużo powtórzeń tego samego kodu, a programiści mogli się wtedy szczycić podejściem: trudno było napisać, trudno będzie zrozumieć.


Maciej Rynkiewicz. Software Development Manager w Wakacje.pl. Inżynier z zamiłowaniem do pracy twórczej, programista, architekt systemów i lider. Potrafi zrozumieć potrzeby biznesu i znaleźć rozwiązanie dobre dla wszystkich. Zawsze stawia na zespół, w którym pracuje i transparentną relację. Ma kilkanaście lat doświadczenia w branży IT, w której zaliczył niejedną porażkę, ale odniósł też wiele sukcesów.


Następnie przyszły lata dwutysięczne, które przyniosły rewolucję pt.: „Lasagne-oriented Architecture”. Prawdziwy przewrót: mamy aplikację podzieloną na warstwy, które świetnie wydzielają obszary działania, czasy świetności MVC. Wspaniałe rozwiązanie do czasu gdy aplikacje tak urosły, że utrzymanie oraz ich rozwój jest trudny i czasochłonny. Na szczęście kilka lat później pojawiły się mikroserwisy, które miały być lekiem na całe zło. I faktycznie były, ale żeby dawać wartość biznesową muszą działać razem, czyli przede wszystkim komunikować się.

Komunikacja w sieci jednak niesie za sobą dużo potencjalnych ryzyk i problemów, bo co jeśli Curl nie nawiąże połączenia, albo aplikacja odpowie błędem 500? Jak to dobrze zaplanować, żeby potem nie mieć problemów?

System rozproszony bez względu na złożoność i wartość biznesową jaką przynosi może mieć podobne problemy, z którymi każdy programista musi umieć sobie poradzić. Podstawa to komunikacja, nie jest odkryciem, że serwery, które ze sobą współpracują muszą „widzieć się” i być w stanie przesłać odpowiednią ilość danych, jak i utrzymywać czasem długie połączenia. Następnie aplikacje muszą „dogadać się”, czyli mieć możliwość połączyć się ze sobą za pomocą protokołu sieciowego i rozumieć, co jedna „mówi” a czego druga „oczekuje”.

Nieoceniona staje się w tym miejscu dokumentacja techniczna, a także kluczowy fakt: czy API posiada wersjonowanie i czy możliwe jest zawarcie kontraktu pomiędzy właścicielami technicznymi obu serwisów. Na koniec zostają te najtrudniejsze obszary, czyli niespodziewane błędy aplikacji, które często są błędami w kodzie lub nieprzewidzianymi przypadkami działania. Nie można zapominać także o błędach w działaniu, które wynikają z flow biznesowego lub zależności od zewnętrznego zasobu np. kolejnego API.

Skoro już znamy problemy jakie mogą nas spotkać porozmawiajmy o rozwiązaniach. Chciałbym wyróżnić trzy najpopularniejsze:

1. Połączenie synchroniczne – przeważnie CURL – najbardziej popularne i często wystarczające rozwiązanie. Warto używać do komunikacji w miejscach, gdzie przesyłamy małą ilość danych, a także API, do którego łączymy się odpowiada szybko i jest stabilne.

Plusy:

  • Proste rozwiązanie, nie wymaga dodatkowej infrastruktury, nie jest skomplikowane,
  • Szybkie w implementacji,
  • Łatwe w debugowaniu błędów.

Minusy:

  • Ograniczony czas połączenia (zgodny z konfiguracją klienta i serwera),
  • Blokuje aplikacje na czas wykonania całej operacji.

2. Komunikacja za pośrednictwem systemu kolejkowania – bardziej skomplikowane rozwiązanie, które wymaga od nas znajomości dodatkowych rozwiązań (np. Kafka, AMQP), do którego odkładamy żądanie obsłużenia dalszej komunikacji identycznej, jak w pkt. 1. Po wykonaniu zadania, skrypt może poinformować aplikację o wykonaniu zadania, jeśli jest taka potrzeba.

Plusy:

  • Asynchroniczne rozwiązanie, które nie blokuje aplikacji,
  • Umożliwia implementację bardziej skomplikowanych rozwiązań bez wpływu na aplikację, której używa użytkownik końcowy,
  • Idealne rozwiązanie dla operacji, które nie wymagają potwierdzenia – można dzięki temu ustawić ponowienia lub odpowiednią reakcję na niepowodzenie,
  • Dobre rozwiązanie dla komunikacji z niestabilnymi serwisami.

Minusy:

  • Złożone technicznie rozwiązanie, które wymaga dodatkowej infrastruktury,
  • Trudniejsze w naprawianiu błędów, debugowaniu i testowaniu.

3. Komunikacja na zasadzie call back’ów – rozwiązanie, które wymaga zaangażowania obu stron, ponieważ cała logika odpowiedzi, na którą nasza aplikacja musi być gotowa, leży po stronie serwera (w komunikacji klient-serwer). Polega to na wysłaniu żądania do serwera, który potwierdza przyjęcie żądania, następnie wykonuje zleconą operację i odpowiada na zdefiniowane API klienta zlecającego.

Dodatkowo konieczne jest ponawianie nieobsłużonych requestów przez serwer, co znacznie komplikuje całość.

Plusy:

  • Rozwiązanie angażujące aplikację klienta do wykonania operacji w minimalnym stopniu,
  • Działanie w pełni asynchroniczne,
  • Proste rozwiązanie techniczne, w stosunku do możliwości jakie daje.

Minusy:

  • Wymaga dodatkowej konfiguracji – adres na jaki mają wpadać odpowiedzi musi być przygotowany i stabilny,
  • Po stronie serwera wymagana jest implementacja ponowień, jeśli serwis nie przyjmuje odpowiedzi.

Każdą funkcjonalność powinno się projektować indywidualnie, tak aby rozwiązywała problemy, a nie tworzyła nowych. Ważne jest też znalezienie złotego środka i nie popadanie w skrajności. Powyższe propozycje rozwiązań są w stanie obsłużyć prawie wszystkie znane mi przypadki, jednak warto dobrze się zastanowić, czy aby nie wyjeżdżamy z armatą na muchę. Wszystkie mają plusy i minusy, oraz każde z nich wymaga zbierania logów i ustawienia odpowiedniej konfiguracji niezbędnej do prawidłowego działania.

Jestem przekonany, że łatwo jest napisać dużo fajnego kodu, ale trudniej jest tworzyć rozwiązania łatwe w utrzymaniu i dostosowane do naszych potrzeb.


Grafika główna artykułu pochodzi od benorama.

Zapraszamy do dyskusji
Nie ma więcej wpisów

Send this to a friend