Pogodzić synchroniczne i asynchroniczne


Dzisiejszy wpis dotyczy programowania w NodeJS w kontekście pewnego specyficznego problemu, którym jest stworzenie serwera LDAP będącego sprytnym proxy do wielu serwerów zewnętrznych niewidzialnych ze strony klienta, który widzi jedno spójne drzewo katalogowe.

Pisanie serwera w NodeJS to generalnie bardzo przyjemna sprawa, bo programowanie sterowane zdarzeniami dobrze się tutaj sprawdza, wiele ułatwia. Ponadto, dostępny jest pakiet ldapjs, który tworzenie serwera usługi LDAP czyni dosyć przyjemnym i nawet niespecjalnie trudnym. Problem pojawia się jednak wtedy, gdy trzeba w NodeJS zaprogramować złożonego klienta LDAP. Pakiet ldapjs posiada odpowiednie rozwiązania do klienckich połączeń, ale ponieważ oparte są one o programowanie sterowane zdarzeniami, to problematyczne staje się zrealizowanie takiego scenariusza, w którym nasz serwer LDAP musi w ramach obsługi żądania odpytać nie jeden lecz kilka różnych serwerów zewnętrznych i ich odpowiedzi złożyć w jedną, którą przekaże klientowi. Dlaczego to taka różnica?

Jeden do jednego

Przy proxy jeden-do-jednego powstaje dosyć łatwa do obsługi sekwencja zdarzeń:

  1. Zachodzi połączenie się z serwerem;
  2. Zachodzi uwierzytelnienie w usłudze LDAP;
  3. Zachodzi przyjęcie żądania wyszukiwania w katalogu;
  4. W wyniku zajścia zdarzenia nr 3 serwer lokalny tworzy zdarzenie połączenia z serwerem zewnętrznym;
  5. W wyniku pomyślnego zajścia zdarzenia nr 4 serwer lokalny tworzy zdarzenie uwierzytelnienia w usłudze LDAP;
  6. W wyniku pomyślnego zajścia zdarzenia nr 5 serwer lokalny tworzy żądanie wyszukania w katalogu zewnętrznym;
  7. W wyniku pomyślnego zajścia zdarzenia nr 6 zachodzi zdarzenie uzyskania wielu odpowiedzi od serwera zewnętrznego (wiele zdarzeń zbiorczo) aż do wystąpienia zdarzenia końca transmisji.

Powyższa sekwencja jest łatwa dlatego, że pomimo bycia złożoną z wielu zdarzeń są one ze sobą powiązane kolejnością w sposób ukryty (zdarzenia 1-3) lub jawny (zdarzenia 4-6), a wielością zdarzeń zbiorczo opatrzonych nr 7 można jeszcze jako tako zarządzać i je opanować, bo nie wywołują one kolejnych wielu zdarzeń.

W przypadku jednak proxy jeden-do-wielu zdarzenia nr 4, 5 i 6 mogą dziać się w bardzo różnej kolejności i mieć różne wyniki a zdarzenie nr 7 staje się trudną do zorganizowania plątaniną wielu zdarzeń, które trzeba scalić w jedną odpowiedź.

Jeden do wielu

Dochodzi więc do sytuacji, gdy trzeba przechwycić wszystkie asynchroniczne zdarzenia i ich wyniki scalić ze sobą. Ponieważ w NodeJS obsługa zdarzeń może się szybko szalenie zagnieżdżać (callback w callback) można próbować sięgnąć po rozwiązania upraszczające takie jak np. promises. Te są dobre dla pewnej stałej liczby zdarzeń, ale przy nieznanej liczbie mocno zagnieżdżających się i przeplatających zdarzeń również są trudne w opanowaniu (a przynajmniej ja miałem z nimi kłopoty w kontekście realizowanego zadania).

Być może istnieją tutaj jakieś wzorce programistyczne czy dobre praktyki, ale rozwiązanie, które ja zastosowałem, ucina problem zagnieżdżania w sposób bardzo jednoznaczny i wprowadza ostateczny porządek. Rozwiązaniem tym jest porzucenie asynchroniczności przy obsłudze połączeń z serwerami zewnętrznymi. Jak to robię? Zwyczajnie wywołuję synchronicznie standardowe ldapsearch z pakietu OpenLDAP. NodeJS daje możliwość wywoływania programów zewnętrznych zarówno w sposób asynchroniczny jak i synchroniczny. Ten drugi wydaje się być wbrew idei, ale właśnie bywają momenty, gdy może on bardzo dużo uprościć. Należy jednak wziąć pod uwagę, że blokuje on działanie całego programu.

Uproszczony schemat rozwiązania z użyciem ldapsearch jest taki:

  1. Zachodzi połączenie się z serwerem;
  2. Zachodzi uwierzytelnienie w usłudze LDAP;
  3. Zachodzi przyjęcie żądania wyszukiwania w katalogu;
  4. Wykonane zostanie jedno lub kilka poleceń ldapsearch a ich wyniki są odpowiednio scalanie.

Tak więc ostatecznie wymyślone przeze mnie proxy LDAP wykorzystuje naturę programowania sterowanego zdarzeniami do stworzenia serwera LDAP w NodeJS, ale przechodzi do zwykłego programowania procedurelnego („which merely acts rather than reacts„) gdy trzeba stać się klientem LDAP i przetwarzać uzyskane odpowiedzi.

 


Dodaj komentarz