Wiesz, iż własna, customowa libka, zbudowana specjalnie dla Twojego mikroserwisowego systemu to tak naprawdę strzał prosto w kolano?
Pewnie pierwsza myśl jaka Ci się pojawi, to:
“Ale dlaczego? Co w tym złego?”
Już tłumaczę.
Zazwyczaj chcemy dobrze. Wychodzimy z założenia DRY, czyli Don’t Repeat Yourself. Nie chcemy się powtarzać. No bo w końcu tak nas wszyscy uczą. Nie duplikuj kodu.
Zasadniczo jest to słuszne.
Jeżeli coś jest skomplikowane, to lepiej to zrobić raz, w jednym miejscu, w libce i jej użyć wszędzie. No i tu pojawia się problem. Nagle, dodajemy twardą zależność do wszystkich mikroserwisów. zwykle zależność, która mocno wymusza konkretny framework i jego wersje. Tracimy możliwość niezależnego rozwoju poszczególnych usług. Betonujemy swój system.
Osobiście spotkałem się z kilkoma rodzajami takich libek:
- Mechanizmy bezpieczeństwa – np. na potrzeby zabezpieczenia komunikacji REST
- Mechanizmy komunikacji – sztandardowym przykładem będą tu wszelkie connectory, np do Kafki
- Utilsy narzędziowe – np. do mechanizmu obsługi feature flag
No to kolejno.
Własne mechanizmy bezpieczeństwa
Podstawowe pytanie tutaj: Ale po co Ci własny mechanizm?
Istnieją szeroko wspierane standardy, które w 99% przypadków będą wystarczające. Naprawdę nie trzeba wymyślać nic nowego. OAuth2 i jego tokeny JWT powinny wystarczyć. Co więcej, Spring Security wspiera wiele protokołów wydawania takich tokenów, więc możesz wybrać taki flow jaki będzie dla Ciebie najlepszy.
Może się tutaj pojawić argument:
“Ale nasz token musi zawierać dodatkowe informacje, dlatego będziemy go generować w każdym serwisie przy użyciu libki”
No i super. Ale to przez cały czas nie usprawiedliwia tworzenia libki.
Stwórz dodatkowy mikroserwis, który będzie wystawiał API zgodne z OAuth2 i wydawał tokeny według Twoich standardów.
Najgorsze co możesz zrobić, to stworzyć libkę która będzie w sobie miała pełną autokonfiguracje warstwy bezpieczeństwa (czyli np. Spring Security). To sprawi, iż totalnie odbierzesz możliwość tuningowania serwisom z niej korzystającym, a co więcej, wymusisz bardzo konkretne rozwiązanie.
Mechanizmy komunikacji
Kafka connector. Rabbit connector. REST XYZ client.
Jeżeli Twój mechanizm komunikacji jest tak skomplikowany, iż wymaga napisania do niego specjalnej libki, to znaczy, iż jest jakiś spory problem gdzieś indziej.
Aktualnie mamy wiele sposobów w jaki możemy się komunikować.
Wyliczę kilka dla samej Kafki:
- Kafka Connect
- Kafka Client
- Kafka Streams
- ksqlDB
- Spring Cloud Stream Kafka i Kafka Streams
No i może jeszcze kilka dla REST:
- Feign
- Retrofit
- WebClient
- RestTemplate
- Apache HttpClient
Sporo tego, prawda?
No i teraz, pisząc własny connector, musisz wybrać, którą technologię narzucisz? A może się okazać, iż dla pewnego mikroserwisu lepsza byłaby inna technologia? I co teraz?
Także, zanim pójdziesz w tę stronę, dobrze się zastanów, dlaczego chcesz to zrobić.
- Czy masz jakieś standardowe nagłówki które mają zostać dodane?
- Czy klucz komunikatu ma mieć określony format?
- Czy payload ma mieć jakąś standardową strukturę?
Wszystkie te rzeczy możesz wymusić poprzez określenie standardów i kontraktów. Możesz użyć specyfikacji Avro lub OpenAPI. Możesz też wykorzystać testy oparte o konktrakty.
A co jeżeli masz jakąś bardziej zagmatwaną logikę? Na przykład, doklejasz jakieś sufixy i prefixy do nazw topików, albo jakoś dziwnie budujesz URL zapytania, doklejając jakieś dziwne parametry query?
W takim przypadku, bym na Twoim miejscu dobrze się zastanowił, dlaczego coś takiego w ogóle robisz. Bo jak dla mnie, jest to totalnie zbędne utrudnianie sobie życia.
Utilsy narzędziowe
Kolejny rodzaj libek, prawdę mówiąc najbardziej sensowny, to narzędzia.
o ile Twoja libka to czyste klasy narzędziowe, np. coś w stylu Apache Commons, Apache lang itp. No to super. To ma to szanse być użyteczne (chyba iż duplikujesz coś, co już jest dostępne na rynku ot tak).
Natomiast, o ile Twoja libka adresuje jakiś szerszy problem, np. “Sposób wykorzystania mechanizmu Feature Flag”, no to już jest gorzej.
Bardzo często, chcąc stworzyć libkę do prostego problemu, zaczynamy budować uniwersalnego potworka do wszystkiego, wręcz framework. Chcemy przewidzieć wszystko. Co powoduje, iż kod i samo użycie robi się mega skomplikowane.
A umówmy się, sprawdzenie Feature Flag to tak naprawdę sprawdzenie IFa. Prosta operacja, jedyna trudność to to, iż źródło prawdy może znajdować się w innym serwisie.
Taki potworek puchnie i puchnie. Jest dla nas dodatkowym obciążeniem, dodatkowym kodem do utrzymania. Co więcej, im więcej usług z niego korzysta, tym bardziej prawdopodobne, iż coś popsujemy przy wydaniu kolejnych wersji libki. Musimy położyć bardzo mocny nacisk na dokładne przetestowanie naszego potworka.
Ale ja jednak chce zrobić libkę!
No dobra, o ile jednak potrzebujesz tę libkę, to trzymaj się tych zasad:
Sprawdź, czy nie ma już gotowego standardu adresującego Twój problem.
Jeżeli jest, wykorzystaj ogólnodostępne biblioteki wspierające ten standard.
Najpierw zdefiniuj standard czy też specyfikację Twojego problemu.
Dzięki temu libka będzie opcjonalna. Każdy w razie czego będzie mógł zaimplementować mechanizm u siebie w swojej technologii.
Stwórz narzędzia, nie rozwiązania.
Spraw, aby Twoja libka dostarczała klas narzędziowych, które możesz swobodnie wpinać w mechanizmy Twojego serwisu. Tak, aby nie wymuszała rozwiązania, a jedynie wspierała.
Unikaj automatycznej konfiguracji.
Nigdy nie wymuszaj swoją libką automatycznej konfiguracji. To jest największe zagrożenie ze strony libek, które o ile jest nieumiejętnie zrobione, bardzo utrudnia rozwój aplikacji.
W niektórych przypadkach może w ogóle zablokować Twoją pracę i wymagać najpierw wprowadzenia poważnych zmian w samej libce.
Pamiętaj, iż jak stworzysz libkę, to musisz ją później utrzymywać, fixować i rozwijać. Nie ma lekko.