Jak rozbić monolit na moduły, a potem na mikroserwisy?

1

W jaki sposób rozbić monolit na moduły, które potem ewentualnie mogłyby stać się mikroserwisami? Słyszałem o różnych technikach typu bounded context, event storming itd, ale w takim monolicie wszystko wydaje się być ze sobą ściśle połączone i nie bardzo wiem, jak się do tego zabrać, np. co zrobić z joinami na kilka tabel? Czy transakcje rozproszone są ok czy unikać ich jak tylko się da? Itd. Pytam ogólnie, ale też w kontekście mojej apki do zarządania kinem, którą chciałbym właśnie zmodularyzować a potem przepisać na mikroserwisy: https://github.com/Sampeteq/cinema-app. Polecacie jakieś tutoriale, szkolenia, książki?

2

Na bardzo małych projektach, to chyba trudno przyłożyć takie patrzenie ... to główny problem tutaj
Wszystkie "aż pięć" tabel się łączy ... gdzie tu cięcie kontekstu

Pomyślałem sobie, że myślenia tabelami nie ułatwia - raczej myślenie encjami *) it d...

``*)` i znów, w małym projekcie trudno odróżnić encje od tabel

1
Nofenak napisał(a):

W jaki sposób rozbić monolit na moduły, które potem ewentualnie mogłyby stać się mikroserwisami?

Nie idź w mikroserwisy, chyba że masz jakiś bardzo dobry powód (w 99% przypadków nie ma).

Zostawienie pojedynczej aplikacji która jest podzielona na moduły w zupełności wystarczy.

Nofenak napisał(a):

Słyszałem o różnych technikach typu bounded context, event storming itd,

"bounded context" to nie technika, tylko określenie jakie elementy w aplikacji są ze sobą "bliższe", bardziej niż "dalsze" - i to występuje zarówno w MS jak i w monolitach. Można powiedzieć że np. github ma różne bounded contexty w okresleniu tego w jakim miejscu jaki użytkownik może coś zrobić. Użytkownik w issues pełni jedną rolę a zmieniają ustawienia pełni inną - tzn. że ustawienia i issues na githubie mają inne bounded contexty. Ale np już użytkownik w issues i pull requests pełni role bardzo podobą, więc można powiedzieć że to jest ten sam bounded context.

Nofenak napisał(a):

ale w takim monolicie wszystko wydaje się być ze sobą ściśle połączone i nie bardzo wiem, jak się do tego zabrać,

Jeśli ktoś robi monolit w którym rzeczy są ze sobą ściśle powiązane, to na 99.9% zrobi "mikroserwisy" w którym wszystko jest ze sobą ściśle powiązane (dlatego że skille potrzebne do zrobienia niezależnych MS, to są te same skille które są potrzebne do zrobienia niezależnych modułów w monolicie + masa narzutu w postaci accidental complexity jak dockery, k8s, http, rest, etc.).

Jak się do tego zabrać - chyba trzeba zlokalizować miejsca w monolicie które się zmieniają razem i powoli je wynosić jako moduły i uniezależniać je od pozostałych części aplikacji. Jak je uniezależnić? Loose-coupling, polimorfizm, abstrakcja, enkapsulacja, dependency inversion, SRP, separation of conerns, dobre nazwy.

Nofenak napisał(a):

np. co zrobić z joinami na kilka tabel? Czy transakcje rozproszone są ok czy unikać ich jak tylko się da? Itd. Pytam ogólnie, ale też w kontekście mojej apki do zarządania kinem, którą chciałbym właśnie zmodularyzować a potem przepisać na mikroserwisy: https://github.com/Sampeteq/cinema-app. Polecacie jakieś tutoriale, szkolenia, książki?

Nie myśl o tym w kontekście bazy, nie za wiele to pomoże.

0

@Riddle Ale ja to robię w ramach nauki, w domu a nie w pracy.

0
Nofenak napisał(a):

@Riddle Ale ja to robię w ramach nauki, w domu a nie w pracy.

No rozumiem, ale nadal - jeśli nie potrzebujesz zespołu 100 programistów którzy chcą koordynować swoją pracę, i jednocześnie MUSZĄ pracować nad jedną aplikacją (nie da się tego podzielić na niezależne projekty), to faktycznie mikroserwisy to jest dobry pomysł. Ale jeśli nie, to monolit w zupełności wystarczy.

0

@Nofenak Nie obejdzie sie bez Event Driven ...

  1. zacznij od razu budowac moduly jako separowane, niezalezne i samodzielne paczki spinane w calosc szkieletem monolitu.
    • pozwoli Ci to rozwijac aplikacje w bezpieczny, decoupled sposob
    • pojedyncze paczki/moduly moga byc w ten sposob efektywnie zarowno rozwijane jak i testowane zarowno w srodowisku aplikacji jak i swoim wlasnym
  2. komunikacje miedzy paczkami/modulami realizuj na poczatku zintegrowanym w monolicie Event Bus'em za pomoca eventow z odpowiednimi interfejsami portow, dla adapterow emiterow i konsumerow w modulach.

W przyszlosci, takie podejscie pozwoli Ci (gdyby nie daj Boze poszczegolne moduly zaczely sie za bardzo rozrastac) plynnie, po kolei spinac monolit z separowanymi w oparciu o moduly mikroserwisami i zewnetrznym Event Bus'em

Czy transakcje rozproszone są ok czy unikać ich jak tylko się da

jesli zakladasz rozwoj aplikacji, i separowanie modulow do mikroserwisow, to nie da sie tego uniknac i lepiej zebys juz od poczatku spial ze wszystkim jakis Event Sourcing, poczatkowo podobnie jak Event Bus, wprost zintegrowany w monolicie, ale z mozliwoscia odseparowania go w przyszlosci jako osobnego mikroserwisu.

Nie wiem jak wJava'ie, ale w php, u nas w zespole realizujemy takie struktury z powodzeniem i radza sobie one z calkiem sporymi wyzwaniami (firma hostingowa).

2
Nofenak napisał(a):

@Riddle Ale ja to robię w ramach nauki, w domu a nie w pracy.

Nie da sie za bardzo
Kajak nie jest pomniejszonym lotniskowcem, i na odwrót

Nigdy nie doświadczysz PRAWDZIWEGO głodu, że uS są potrzebne, problemów wydajnościowych, zrywania połączeń, bezpieczników, ubijania i podnoszenia instancji uS (w tym wielokrotnych) itd...

W realnej pracy np NA PEWNO wystąpią odejścia od śliczych teretycznie założeń (bo np wydajność bije w nos, złożoność jedzie pod sufit - a cięcie nie jest praktrycznie uzasadnione)

2

Podstawą jest wydzielić odpowiednio bounded contexty. Jak to zrobić samemu w domu? Trudno, możesz pobawić się sam ze sobą w event storming czy podobne rzeczy, ale efekt będzie za pewne mizerny, zwłaszcza jak nie masz w tym doświadczenia bo potrzebna jest tu wiedza biznesowa, czyli jak robisz swój system do obsługi kin, to powinieneś gadać z kimś kto rozumie i wie jak ten biznes działa.

Jak już masz bounded contexty to możesz zacząć wydzielać granice w monolicie, które nazywasz modułami. W idealnym świecie konteksty nic o sobie nie wiedzą. Mają osobne modele domenowe, serwisy, polityki, repozytoria, itd. Do komunikacji używają jakiegoś języka publicznego, zwykle są to eventy, ale mogą też być komendy - w sensie, że jeden kontekst wysyła komendę do drugiego by coś zrobił. Są dobre prezentacje na ten temat od Łukasza Szydło i Sławka Sobótki na yt.

I to by było na tyle. Kwestia implementacji komunikacji pomiędzy kontekstami to pewnie jakaś szyna in-memory w .net mamy bibliotekę https://github.com/jbogard/MediatR do takich rzeczy, w Javie pewnie też coś takiego znajdziesz.

1

Zaprojektuj swoje moduły tak, że każdy moduł odpowiada jednemu mikroserwisowi. Główną zasadą powinno być takie developowanie, że w razie potrzeby wyciągnięcie modułu do osobnego mikroserwisu powinno być trywialne. Projektując kod ciągłe myśl ile operacji na kodzie trzeba wykonać, żeby wyciągnąć moduł do osobnej aplikacji. Stąd:

  • żadnych joinów pomiędzy bazami, najlepiej zrób osobne logiczne bazy, żeby nie kusiło (jak nie ma joinów to bazy nie są połączone i można je spokojnie przenieść na inny serwer bazodanowy)
  • postaraj się ograniczyć tranzakcje poprzez lepszy design, który ich nie wymaga lub np. saga pattern
  • trzymaj wspólne dane takie jak. np wymienianie wiadomości w osobnych modułach (w razie wyciągnięcia modułu do mikroserwisu wystarczy skopiować katalog)
  • idź bardziej w dynamiczną stronę programowania. Zamiast wywoływać metodę na wstrzykniętym interfejsie lepiej jest wywołać zapytanie HTTP (w razie wyciągnięcia modułu wystarczy podmienić adres IP zamiast przerabiać budowanie drzewa zależności)

Oczywiście część tych zasad można nagiąć np. możesz olać komunikację poprzez HTTP/kolejki, jeśli uznasz, że użyjesz ekwiwalentu, który działa tak samo dla monolitu np. wspomniany wstrzyknięty interfejs albo jakaś kolejka in-memory

1

@slsy Zgadzam się z wszystkim oprócz jednego.

slsy napisał(a):
  • idź bardziej w dynamiczną stronę programowania. Zamiast wywoływać metodę na wstrzykniętym interfejsie lepiej jest wywołać zapytanie HTTP (w razie wyciągnięcia modułu wystarczy podmienić adres IP zamiast przerabiać budowanie drzewa zależności)

To jest słabe - bo jeśli faktycznie "przerabianie drzewa zależności" jest skomplikowane, to te moduły od początku były źle zrobione. Podmianka interfejsu powinna być trywialna. Więc nie ma sensu dodawać HTTP za wczasu.

1
Riddle napisał(a):

To jest słabe - bo jeśli faktycznie "przerabianie drzewa zależności" jest skomplikowane, to te moduły od początku były źle zrobione. Podmianka interfejsu powinna być trywialna. Więc nie ma sensu dodawać HTTP za wczasu.

Może trochę przesadziłem. Chodzi mi o taki przykład

facade_a = create_facade_a(args)
facade_b = create_facade_b(other_args, facade_a)

vs

facade_a_url = create_facade_a(args)
facade_b_url = create_facade_b(other_args, facade_a_url)

W drugim przypadku jedynie wystarczy zmienić URL (w przypadku migracji), gdzie w pierwszym przypadku trzeba zaimplementować interfejs facade_a przy użyciu klienta HTTP. Zgadzam się, że w przypadku takiego modularnego monolitu jest to sztuka dla sztuki (bo wady wielokrotnie przebijają zalety), ale warto wiedzieć, że taki zabieg zmniejsza coupling pomiędzy modułami

1
slsy napisał(a):
Riddle napisał(a):

To jest słabe - bo jeśli faktycznie "przerabianie drzewa zależności" jest skomplikowane, to te moduły od początku były źle zrobione. Podmianka interfejsu powinna być trywialna. Więc nie ma sensu dodawać HTTP za wczasu.

Może trochę przesadziłem. Chodzi mi o taki przykład
[...]
W drugim przypadku jedynie wystarczy zmienić URL (w przypadku migracji), gdzie w pierwszym przypadku trzeba zaimplementować interfejs facade_a przy użyciu klienta HTTP. Zgadzam się, że w przypadku takiego modularnego monolitu jest to sztuka dla sztuki (bo wady wielokrotnie przebijają zalety), ale warto wiedzieć, że taki zabieg zmniejsza coupling pomiędzy modułami

A czemu te moduł miałyby cokolwiek wiedzieć o URL'ach?

Przecież jeżeli wstrzykujesz interfejs, to równie dobrze mógłbyś tam wsadzić implementację która kieruje ruchem po HTTP, tak że użytkownicy tego interfejsu nie musieliby nigdy wiedzieć że działają po networku.

Ps: zgadzam się że url jest na tyle flexible że może się odnieść do plików, networków, maili, no ale i tak to nie jest tak elastyczne jak interfejs programistyczny, więc why bother?

2

Warto odpowiedzieć sobie na pytanie czego właściwie oczekuje się od modułu poza nazywaniem. Jakie korzyści ma taki podział na moduły dać?

Ja oczekuję, że jak przeprowadzam zmianę w module A to ta zmiana jest ograniczona swoim zasięgiem tylko do modułu A. Nie chcę dopuścić do sytuacji, że poprawiając moduł A poprawiam moduł B, a po drodze jeszcze moduł C. Jeśli tak ma być to równie dobrze mógłbym funkcjonować bez modułów, bo w rezultacie wyszło by na to samo.

Jestem zdania, że kod warto dzielić wg biznesowego zastosowania. Te tematy, które są ze sobą silniej sprzężone znajdowałyby się w tym samym module, bo wzajemnie na siebie działają i co najważniejsze upraszczają komunikację (więc nie matwię się o API i jego łamanie).

W komunikacji między modułami staram się wytypować moduł nadrzędny (ten który będzie wołał moduł B, ale sam nie będzie wołany). Jeśli nie mam takiej sytuacji to wolę jeden większy moduł niż dwa z cykliczną zależnością.

1 użytkowników online, w tym zalogowanych: 0, gości: 1