Jak dobrze podzielić program na moduły lub mikroserwisy?

0

W jaki sposób dzielić aplikację na moduły (modularny monolit) lub mikroserwisy w optymalny sposób? Wiem, że jest to związane z bounded contextem czy domeną jak zwał, tak zwał, ale szukam konkretnych wskazówek i przykładów. Takie 2 głównie problemy jakie widzę, to raz, żeby nie skończyć z rozproszonymi monolitem czyli sytuacją kiedy każdy moduł/mikroserwis ściśle zależy od całej reszty i muszą być wdrażane jednocześnie, a 2 żeby też nie zrobić za dużych modułów/mikroserwisów, które ciężko ogarnąć i rozwijać. Jak to wyważyć? W ogóle czy jeden moduł/mikroserwis powinien ciągnąć dane z drugiego? Czy komunikacja synchroniczna nie powinna być opcjonalna i komponenty nie powinny się komunikować tylko eventami czy czymś podobnym?

4

Mikroserwisy mają wiele problemów, które trzeba rozwiązywać wynikające z komunikacji między nimi więc jeśli to mały projekt (poniżej 10 programistów) sugerowałbym raczej monolit dobrze podzielony na moduły.

Nie ma uniwersalnego podejścia co do modułów ale warto się skupić na tym, żeby nie było sytuacji, że moduł A korzysta z modułu B a moduł B korzysta z modułu A

4

@Nofenak:

W jaki sposób dzielić aplikację na moduły (modularny monolit) lub mikroserwisy w optymalny sposób?

Na podstawie wymagań biznesowych.

Wiem, że jest to związane z bounded contextem czy domeną jak zwał, tak zwał,

Nie. Pojęcie domena i bounded context to nie są tożsame pojęcia. W ramach jednej domeny może występować kilka bounded contextów lub/oraz poddziedzin ponieważ nie zawsze (poza światem idealnym) bounded context mapuje się 1:1 na poddziedzinę.

Takie 2 głównie problemy jakie widzę, to raz, żeby nie skończyć z rozproszonymi monolitem czyli sytuacją kiedy każdy moduł/mikroserwis ściśle zależy od całej reszty i muszą być wdrażane jednocześnie, a 2 żeby też nie zrobić za dużych modułów/mikroserwisów, które ciężko ogarnąć i rozwijać. Jak to wyważyć?

Nigdy nie zaczynamy od mikroserwisów. Zacząć od modularnego monolitu jak wspomniał @anonimowy i wydzielać moduły jako mikroserwisy tylko jeżeli zajdzie potrzeba biznesowa. W przeciwnym wypadku to nic innego jak onanizm techniczny.

W ogóle czy jeden moduł/mikroserwis powinien ciągnąć dane z drugiego? Czy komunikacja synchroniczna nie powinna być opcjonalna i komponenty nie powinny się komunikować tylko eventami czy czymś podobnym?

A to zależy. Od czego? Od wielu czynników. Jak popatrzymy na system z punktu widzenia domain driven design, to zobaczymy, że mamy różne typy poddziedzin: główne (core subdomains), wspierające (supporting domains) oraz generyczne (generic subdomains). Jeżeli rozważymy sobie system w którym główną rolę gra dziedzina związana z analizą danych (core subdomain) to konieczność wystawienia faktury dla klienta, być może według prawa podatkowego obowiązującego w jego kraju, to w przypadku rozwiązania obsługującego klientów z wielu krajów może być problematyczne. W tej sytuacji ostanie czego chcemy to zajmować się implementacją reguł podatkowych według kraju/kontynentu. Dlatego w tej hipotetycznej sytuacji poddziedzina fakturowania (invoicing) będzie domeną generyczną i dla dziedzin generycznych kupujemy gotowe rozwiązanie. W naszym przypadku system księgowy, który integrujemy z naszym systemem. W tej sytuacji może się okazać że system księgowy nie wspiera integracji w stylu event-driven, tylko wystawia proste synchroniczne API po HTTP do którego musimy wysłać request i czekać na odpowiedź. Odpada więc możliwość bezpośredniej integracji opartej o zdarzenia.

Do tego asynchroniczna (przez kolejkę/message broker) integracja za pomocą wiadomości wprowadza duplikację danych. Bo jeżeli wysyłamy wiadomość z A do B (na przykład o kliencie) to zakładamy że B posiada kopię danych tego klienta i nie musi wykonywać już zapytania o jego dane do przykładowo C.

I tak dalej i tak dalej.

A jak to cię nie przekonuje to popatrz na success story takich firm jak Netflix, Amazon, Google, Microsoft, Instagram, Facebook, czy Allegro. Każda z tych firm zaczynała jako monolit ponieważ ma najszybszy time to market. Jest różnica gdy jako platforma streamingowa obsługujesz kilkuset użytkowników, a kilka milionów. W pierwszym przypadku dobrze napisany monolit wystarczy. W drugim już niekoniecznie.

Wszystkie wspomniane firmy skalowały się i ich architektura ewoluowała wraz ze wzrostem biznesu, ponieważ widzieli które moduły/poddziedziny wymagają indywidualnego podejścia.

Dlatego zrób modularny monolit i obserwuj aplikację. Widzisz że 70% ruchu w twojej aplikacji to requesty do katalogu produktów czy modułu streamingowego, który wymaga niezależnego skalowania aby obsłużyć przychodzący ruch bez potrzeby skalowania pozostałych modułów, to wydzielasz go jako osobny serwis. Z biegiem czasu widzisz że biznes wymaga aby lepiej dopasowywać rekomendacje twoich produktów użytkownikom i wprowadzać modyfikacje do algorytmu niezależnie od pozostałych modułów/poddziedzin? Proszę bardzo, wydzielasz poddziedzinę rekomendacji do osobnego serwisu.

I tak dalej i tak dalej.

0

@markone_dev: Imo samo skalowanie to za mało żeby stwierdzić, że potrzebny jest mikroserwis. Bo przecież jak zeskalujesz sobie monolit to on obsłuży też ten moduł a jedyny narzut to tak naprawdę większa aplikacja i te kilka/kilkaset MB rozmiaru projektu

2
anonimowy napisał(a):

@markone_dev: Imo samo skalowanie to za mało żeby stwierdzić, że potrzebny jest mikroserwis. Bo przecież jak zeskalujesz sobie monolit to on obsłuży też ten moduł a jedyny narzut to tak naprawdę większa aplikacja i te kilka/kilkaset MB rozmiaru projektu

Wiadomo, że skalowanie to nie jedyny driver do wydzielania modułu aplikacji jako osobnej usługi. Ale w kontekście tego skalowania to trzeba sobie odpowiedzieć czy chcemy i jest sens skalować całą aplikację, czy tylko jakiś jej moduł, np odpowiedzialny za streaming czy konwersję dużych dokumentów, który może się wywalać i/albo wydajnościowo wpływać na inne moduły.

Generalnie chodzi mi o to, że najlepiej zacząć od monolith first i stopniowo migrować w stronę mikroserwisów jeśli jest taka potrzeba w miarę jak poznajemy domenę biznesową, wymagania użytkowników, zachowanie aplikacji w rzeczywistym środowisku, itd.

0
Nofenak napisał(a):

Takie 2 głównie problemy jakie widzę, to raz, żeby nie skończyć z rozproszonymi monolitem czyli sytuacją kiedy każdy moduł/mikroserwis ściśle zależy od całej reszty i muszą być wdrażane jednocześnie, a 2 żeby też nie zrobić za dużych modułów/mikroserwisów, które ciężko ogarnąć i rozwijać. Jak to wyważyć?

Zapoznaj się z Dependency Inversion oraz Open/Close, to będzie dobre miejsce startowe.

Kilka rules of thumb:

  • Jeśli jakieś elementy zmieniają się razem: powinny być w jednym module, jeśli zmieniasz jakiś element A, ale nie musisz zmienić B, to to jest sygnał że A i B mogą być w osobnych modułach
  • Jeśli dwa kawałki kodu odpowiadają za różne elementy (np persystencję i widok), to mogą być w osobnych modułach
  • Jeśli kawałek kodu korzysta z dużej biblioteki, a inny nie, to sygnał że te dwa kody mogą (ale nie muszą) być w innych modułach.
Nofenak napisał(a):

W ogóle czy jeden moduł/mikroserwis powinien ciągnąć dane z drugiego?

Tak, to jest naturalne.

Dane mogą (i nawet powinny) płynąć z jednego modułu do drugiego, ważne jest tylko żeby ograniczyć coupling-tych modułów.

Nofenak napisał(a):

Czy komunikacja synchroniczna nie powinna być opcjonalna i komponenty nie powinny się komunikować tylko eventami czy czymś podobnym?

Nie koniecznie, nie jest to wymóg.

Co do samych mikroserwisów, to je zostawiłbym na sam koniec: Decyzja o mikroserwisach powinna być podjęta na samym końcu. Jeśli chcesz zrobić krok w stronę dobrej modularyzacji, to powinieneś zrobić tak:

  • Najpierw rozdziel swój kod na sensowne moduły
  • Jeśli to nie wystarczy, rozdziel swój kod na osobne projekty które korzystają z siebie nawzajem
  • Jeśli znowu to nie wystarczy, to uruchom swoją aplikacje w różnych procesach
  • Dopiero jeśli każde z tych metod zawiedzie, spróbuj przerobić je na mikroserwisy.

Spoko filmik, polecam: youtube.com/watch?v=zzMLg3Ys5vI.

0

@Riddle: Przecież autor właśnie o to pyta.

Q: Jak sensownie rozbić projekt na moduły?
A: Najpierw rozdziel swój kod na sensowne moduły

Gorzej jak Chat-GPT :P

0

Dobra modułowość to w gruncie rzeczy minimalizowane zależności pomiędzy różnymi kawałkami kodu. Jak masz dobrą modułowość to:

  • dużo kodu jest pochowana np. za interfejsem. Im mniej kodu widocznego na zewnątrz tym mniejsza szana, że ktoś go użyje. Dobrym kontrprzykładem jest popularny antypattern package by layer, gdzie robisz pakiety model/service/controller. W takim podejściu wszystko gada ze wszystkim, prawidłowym podejściem jest package by feature np. order/ticketing
  • powiązania pomiędzy modułami są słabe. Np. przekazujemy id jakiegoś obiektu z innego modułu zamiast całego obiektu
  • liczba powiązań jest mała, widać klastry.

Dobrym przykładem jest też usuwalność/przenaszalność kodu tj. jeśli trzeba usunąć dany feature to jak prosto da się zrobić? W idealnym przypadku będzie to rm -rf moduł i usunięcie wywołań. Jeśli trzeba przeorać cały kod to jest słabo

0
slsy napisał(a):
  • powiązania pomiędzy modułami są słabe. Np. przekazujemy id jakiegoś obiektu z innego modułu zamiast całego obiektu

To akurat nie o dobrej modularności, bo ten id to nadal jest zależność.

0

Mówię o sytuacji gdy przekazujemy id zamiast całego obiektu. Masz pomysł jak można to zrobić lepiej?

0
slsy napisał(a):

Mówię o sytuacji gdy przekazujemy id zamiast całego obiektu.

A ja Ci mówię że przekazanie jednego id to również jest zależność i powiązanie między modułami. Nie ma znaczenia czy przekażesz cały obiekt czy sam id - masz taki sam coupling w obu case'ach.

Właściwie to id jest gorszy, bo taki obiekt przynajmniej ma namespace i widać explicitly coś coś jest "nie halo", a taki id (int czy long) to prymityw, i nie widać że jest to powiązanie.

slsy napisał(a):

Masz pomysł jak można to zrobić lepiej?

No tak, zniwelować tą zależność stosując odpowiednią enkapsulację.

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

Masz pomysł jak można to zrobić lepiej?

No tak, zniwelować tą zależność stosując odpowiednią enkapsulację.

Jakie są sposoby na to? Jak napisać mikroserwis do dodawania i wyświetlania komentarzy pod wpisem na blogu w taki sposób, by ten mikroserwis nie wiedział o ID wpisu?
https://khorikov.org/posts/2021-02-03-should-you-reuse-ids-between-mss/ to dobrze oddaje jakie ja mam podejście do tego tematu.

0
iksde napisał(a):
Riddle napisał(a):
slsy napisał(a):

Masz pomysł jak można to zrobić lepiej?

No tak, zniwelować tą zależność stosując odpowiednią enkapsulację.

Jakie są sposoby na to? Jak napisać mikroserwis do dodawania i wyświetlania komentarzy pod wpisem na blogu w taki sposób, by ten mikroserwis nie wiedział o ID wpisu?

Jeśli nie wiesz jak poprawnie enkapsulować moduły i mikroserwisy od siebie, to ten id to jest Twój najmniejszy problem.

Żeby coś można było nazwać "mikroserwisem", to musi to być niezależny i autonomiczny element - jeśli to coś wystawia id do innych aplikacji (które można błędnie nazwać "mikroserwisem"), to to coś nie jest i nie może być mikroserwisem - mikroserwisy mają być niezależne od siebie, powinieneś zawsze móc go usunąć i napisać nowy, tak żeby inne się nie zepsuły.

Powiem to prosto z mostu:

Jeśli masz dwie aplikacje które gadają po HTTP/REST, i wymieniają miedzy sobą id, to te dwie aplikacje nie są mikroserwisami. Są po prostu jedną aplikacją podzielonymi na dwa procesy z interfejsem HTTP pomiędzy nimi.

Brutalna prawda:

Jeśli nie do końca rozumiesz Dependency Inversion, to obawiam się że nie masz zbytnich szans na zaprojektowanie dobrego mikroserwisu.

iksde napisał(a):

https://khorikov.org/posts/2021-02-03-should-you-reuse-ids-between-mss/ to dobrze oddaje jakie ja mam podejście do tego tematu.

No, i tutaj jest dokładnie opisane to co powiedziałem. Artykuł który podlinkowałeś jest wręcz tutorialem, który równie dobrze mógłby się nazywać: "Jak złamać enkapsulację w mikroserwisach?".

Jest tam napisane, żeby - jeśli przetwarzasz informacje o cusomterach w dwóch mikroserwisach - to one powinny posługiwać się jednym id (nie ma tam wyjaśnione czemu, jest tylko napisane żeby tak robić). Domyślam się że ma to być "pragmatyczne", czyli ktoś uwidacznia szczegóły implementacyjne jednego mikroserwisu, żeby lepiej kontrolować co się dzieje - ale niestety tym samym łamie enkapsulację, tym samym sprawiając że te dwie aplikacje nie są niezależne od siebie, ergo nie są mikroserwisem.

1

@Riddle: W takim rozumowaniu 95% firm, które mają mikroserwisy tak naprawdę ich nie mają i autor niczego się nie nauczy. Domyślam się, że jednym z celów autora jest lepsze obracanie się w takiej architekurze, którą wykorzystują realne firmy a nie Ty w jednym projekcie... Allegro w takim razie też nie ma mikroserwisów XD Olx to samo.

2
anonimowy napisał(a):

@Riddle: W takim rozumowaniu 95% firm, które mają mikroserwisy tak naprawdę ich nie mają i autor niczego się nie nauczy. Domyślam się, że jednym z celów autora jest lepsze obracanie się w takiej architekurze, którą wykorzystują realne firmy a nie Ty w jednym projekcie... Allegro w takim razie też nie ma mikroserwisów XD Olx to samo.

No, właściwie tak. Podobnie jak 95% firm myśli że jest Agile a nie jest, myśli że ma MVC a nie ma, albo że mając testy stosuje TDD. Rynek IT bardzo lubi takie modne słówka właśnie jak microserwis, sprint, etc. ale nie wystarczy sobie nazwać czegoś microserwisem żeby nim był. "Realne firmy" mają taką architekturę jaką są w stanie wytworzyć ich najlepsi programiści, a Ci często nie znają różnicy między dependency injection i dependency inversion. Ameryki nie odkryłeś.

Mikroserwisy są tworzone przez programistów którzy mają bardzo dobrą zdolność do oceny informacji i projektowania systemów, również dlatego tak dobrze się skalują. Kiedy ten koncept jest adoptowany przez ludzi którzy nie do końca wiedzą co robią, wtedy wychodzi... coż. dwie niepotrzebne apki które gadają po HTTP.

Żeby podejść do modularyzacji należy być ekspertem w Dependency Inversion oraz enkapsulacji, bez tego aplikacja jest raczej mikroserwisem tylko z nazwy.

1

Nie zapędzaj się. Myślę, że większość wie co to TDD jak i MVC. To, że Ty masz swoją definicję mikroserwisów nie znaczy, że tacy giganci jak Amazon, Google, Microsoft się myli płacąc swoim ekspertom czasem więcej niż 10 krotność naszych wypłat. Tak jest i należy to przyjąć za definicję a nie walczyć z wiatrakami i przekonywać innych, że nikt nie korzysta z mikroserwisów bo robią to inaczej niż Ty

0
anonimowy napisał(a):

Nie zapędzaj się. Myślę, że większość wie co to TDD jak i MVC.

Każdy kto pisze testy myśli że zna TDD, ale mało kto faktycznie to zna.

anonimowy napisał(a):

To, że Ty masz swoją definicję mikroserwisów nie znaczy, że tacy giganci jak Amazon, Google, Microsoft się myli płacąc swoim ekspertom czasem więcej niż 10 krotność naszych wypłat. Tak jest i należy to przyjąć za definicję a nie walczyć z wiatrakami i przekonywać innych, że nikt nie korzysta z mikroserwisów bo robią to inaczej niż Ty

No akurat Netflix i Google ma mikroserwisy z prawdziwego zdarzenia: oddzielnie deployowalne, niezależne od siebie, autonomiczne, bardzo luźno powiązane, ze swoim bounded context, jest ich dużo i są małe. Takie mikroserwisy, to są prawdziwe mikroserwisy. Tylko niestety to co robi Netflix i Google, w porównaniu do tego co się robi w innych firmach to niebo a ziemia.

anonimowy napisał(a):

Nie zapędzaj się. Myślę, że większość wie co to TDD jak i MVC. To, że Ty masz swoją definicję mikroserwisów nie znaczy, że tacy giganci jak Amazon, Google, Microsoft się myli płacąc swoim ekspertom czasem więcej niż 10 krotność naszych wypłat. Tak jest i należy to przyjąć za definicję a nie walczyć z wiatrakami i przekonywać innych, że nikt nie korzysta z mikroserwisów bo robią to inaczej niż Ty

Nie odbierz mnie źle - mikroserwisy to jest bardzo dobry pomysł - ale pod warunkiem że tworzą go ludzie którzy wiedzą co robią. Nie wystarczy sobie wziąć aplikacji, przekroić ją na pół, dodać do nich komunikacje po REST żeby mieć mikroserwis ;| Dwie apki które gadają po REST to jeszcze nie mikroserwis.

0

Dlaczego oceniasz wszystko na podstawie słabych firm, w których pracowałeś? Ja nie spotkałem się jeszcze z kimś kto nie rozumie TDD.

Jeśli pracowałeś w Netflixie I Google w działach, w których mieli idealne mikroserwisy to spoko ale ja mówię o całej firmie a nie tylko udanych mikroserwisach

2

@Riddle

Jest tam napisane, żeby - jeśli przetwarzasz informacje o cusomterach w dwóch mikroserwisach - to one powinny posługiwać się jednym id (nie ma tam wyjaśnione czemu, jest tylko napisane żeby tak robić). Domyślam się że ma to być "pragmatyczne", czyli ktoś uwidacznia szczegóły implementacyjne jednego mikroserwisu, żeby lepiej kontrolować co się dzieje - ale niestety tym samym łamie enkapsulację, tym samym sprawiając że te dwie aplikacje nie są niezależne od siebie, ergo nie są mikroserwisem.

O ile z większością się zgadzam, tego zupełnie nie rozumiem. W jaki sposób dzielenie się id jest łamaniem enkapsulacji? W jaki inny sposób chciałbyś to zaimplementować? Jak chciałbyś wiedzieć, o którego customera dokładnie chodzi? To tak jakbyś stwierdził, że mikroserwisy dzielące się np. PESELem pacjenta, czyli unikalnym i jednoznacznie reprezentującym dany obiekt łamią enkapsulację, bo wypuszczają swoje szczegóły implementacyjne.

0
dargenn napisał(a):

O ile z większością się zgadzam, tego zupełnie nie rozumiem. W jaki sposób dzielenie się id jest łamaniem enkapsulacji?
W jaki inny sposób chciałbyś to zaimplementować? Jak chciałbyś wiedzieć, o którego customera dokładnie chodzi?

W taki sposób żebyś mógł dokonać dowolnej zmiany w jednym mikroserwisie, tak żeby drugi mikroserwis nie wymagał zmian. Klasyczna enkapsulacja.

To że ktoś z was nie rozumie jak to poprawnie zaimplementować, nie jest argumentem za tym że to nie jest łamanie enkapsulacji. Bo jest.

dargenn napisał(a):

To tak jakbyś stwierdził, że mikroserwisy dzielące się np. PESELem pacjenta, czyli unikalnym i jednoznacznie reprezentującym dany obiekt łamią enkapsulację, bo wypuszczają swoje szczegóły implementacyjne.

To zależy do czego używasz tego PESEL'a. Jeśli to jest wartość biznesowa, element funkcjonalności faktycznie wymagany przez użytkowników, to to jest dana - i musisz ją przekazać. Wtedy nie istnieje sposób żeby to pominąć, więc musisz przekazać wszędzie PESEL. Jeśli natomiast używasz tego PESEL'a jako identyfikator w Twojej implementacji, to wtedy to jest szczegół i jak on Ci "wycieknie" z Twojego interfejsu to łamiesz enkapsulacje.

Twoje pytanie jest trochę zbyt niskopoziomowe, to tak jakbyś spytał czy "zwrócenie jakiegoś timestampa jest łamaniem enkapsulacji?". Cała odpowiedź brzmi: to zależy jakiego timestampa, czy ten timestamp jest elementem Twojej implementacji czy faktycznie daną biznesową.

W sercu enkapsulacji leży umiejętność rozdzielenia rzeczy które są istotne od tych które nie są - to nie jest czarno-białe, musisz rozumieć sens aplikacji którą piszesz i wiedzieć dokładnie co ona robi i po co jest napisana. Innymi słowy musisz wiedzieć które dane są istotne, a które nie. idki bardzo często nie są - są szczegółem implementacyjnym.

Zadaj sobie pytanie, czy jakbym zmienił te id z liczb na stringi, na UUID albo cokolwiek innego, to czy aplikacja nadal by działała poprawnie? No pewnie że tak. Czy gdybym zmienił każdy id dodając teraz +1000 do każdego wszędzie, czy cokolwiek by się zepsuło? No pewnie nie. A więc to szczegół implementacyjny. Czy jeśli zmieniłbyś PESEL na inną liczbę albo dodał do jakiegoś pesela + 1000, czy aplikacja wtedy by działała poprawnie? No pewnie nie. A więc to nie jest szczegół implementacyjny.

anonimowy napisał(a):

Dlaczego oceniasz wszystko na podstawie słabych firm, w których pracowałeś?

To w większości nie były słabe firmy, ale niemniej niektóre antypatterny są bardzo zakorzenione w wielu miejscach.

anonimowy napisał(a):

Ja nie spotkałem się jeszcze z kimś kto nie rozumie TDD.

Bardzo ciężko mi w to uwierzyć. Tak bardzo, że zaczynam podejrzewać że Ty sam tego nie rozumiesz. Taka umiejętność zajmuje lata żeby ją nabyć. Większość ludzi myśli że TDD to po prostu posiadanie testów.

1

Czyli w skrócie, według Ciebie @Riddle mikroserwisy nie mogą się ze sobą komunikować w żaden sposób (bo jeśli wyłączymy jeden to drugi ma działać identycznie jak działał)?

3
anonimowy napisał(a):

Czyli w skrócie, według Ciebie @Riddle mikroserwisy nie mogą się ze sobą komunikować w żaden sposób (bo jeśli wyłączymy jeden to drugi ma działać identycznie jak działał)?

Nie no, chodzi o to, że działanie jednego mikroserwisu nie może zależeć od drugiego. Jak mikroserwis A uderza no. poprzez RPC do mikroserwisu B, to jeśli B leży, A nie może z tego powodu rzucić błędu. Np. Netfliksowi jak padnie serwis do spersonalizowanych rekomendacji filmów, mogą pokazać najczęściej oglądane lub najlepiej oceniane. Nie będzie to idealne, bo być może user woli komedie a najczęściej oglądane są horrory, ale będzie jakoś działać.

0

No i jak to się ma do przekazywania ID? Netflix przekazuje filmy za pomocą ID właśnie więc nie jest to według @Riddle mikroserwis więc proszę o inny przykład "prawdziwego" mikroserwisu

0
anonimowy napisał(a):

No i jak to się ma do przekazywania ID? Netflix przekazuje filmy za pomocą ID właśnie więc nie jest to według @Riddle mikroserwis więc proszę o inny przykład "prawdziwego" mikroserwisu

Popełniasz dwa błędy:

  • Po pierwsze, to że Ty jako użytkownik widzisz id filmu, nie znaczy że mikroserwisy między sobą używają tego id
  • Po drugie, to że ID filmu jest przekazywane np z frontu do backendu, to nie znaczy jeszcze że ten id jest zależnością tego mikroserwisu

Widzę, że po prostu nie kumasz o co chodzi z enkapsulacją. Aplikacja może używać/przetwarzać jakąś daną, ale w taki sposób żeby od niej nie zależała.

EOT

Dobra, już mi się nie chcę tłumaczyć tego samego po piąty raz.

Do wszystkich którzy myślą że mikroserwisy mogą od siebie zależeć: tematy do powtórzenia: 1. enkapsulacja 2. dependency inversion 3. loose-coupling.

0

No to sam sobie zaprzeczasz. Nie da się dyskutować w takim wypadku. To, że aplikacja używa ID nie znaczy, że od niej zależy czy znaczy, że zależy? Bo wcześniej twierdziłeś, że zależy.

Przecież architektura Netflixa jest dostępna (jest wiele artykułów i filmów od pracowników) i używają właśnie m.in ID

0
anonimowy napisał(a):

To, że aplikacja używa ID nie znaczy, że od niej zależy czy znaczy, że zależy?

@anonimowy: Zapoznaj się z tematem "Dependency Inversion" i wtedy pogadamy.

0

No to chyba Ty właśnie nie znasz skoro twierdzisz, że przekazanie ID łamie tę zasadę

3

Aby podzielić program na moduły czy serwisy to patrz głównie na dane i na związki między nimi.

Jeśli moduł ma zbyt mało danych, aby zrealizować zadanie to jest bardziej zależy od innych modułów. Wtedy potrzeba więcej komunikacji, interfejsów, pośrednich typów, a najgorsze, że to wszystko jest specyficzne dla biznesu i będzie się zmieniać w czasie.

Zostawiam dwa ciągle aktualne cytaty w temacie:

Bad programmers worry about the code.
Good programmers worry about data structures and their relationships.
---- Linus Torvalds

Data dominates. If you’ve chosen the right data structures and organized things well,
the algorithms will almost always be self-evident.
Data structures, not algorithms, are central to programming.
---- Rob Pike

2
Nofenak napisał(a):

W jaki sposób dzielić aplikację na moduły (modularny monolit) lub mikroserwisy w optymalny sposób?

Zależy jakie kryteria tej optymalności sobie przyjmiesz i w jakim celu rozbijasz całość na mikroserwisy. Żeby sensownie zrobić system na mikrousługach, musisz sporo zainwestować w devops, bo serio, nie chcesz tego ręcznie składać do kupy na VM'kach. To taka oderwana uwaga, ale widywałem próby robienia systemów na mikrousługach i brak umiejętności pracy z infrastrukturą szybko weryfikował te pomysły.
To czym możesz się kierować, to podobnie jak w przypadku modułów aplikacji poziom splątania z innymi. Im bardziej "zwarte" interface'y możesz wystawić, tym prawdopodobnie lepiej. Po ludzku - mniej metod w serwisie X, które są potrzebne serwisowi Y. Są też wymagania biznesowe/domena. Jeżeli masz system do fakturowania, to prawdopodobnie słownik klientów może być zarządzany przez inną usługę niż repozytorium faktur, bo można dość prosto zdefiniować API. Jeżeli system ma się komunikować z systemem księgowym, to prawdopodobnie warto również usługę za to odpowiedzialną zrobić osobno, bo znowu - masz jasne API tej usługi, a jeżeli firma podejmie decyzję o zmienie tego systemu, ty będziesz zmuszony dokonać zmian (albo napisać od zera) tylko jeden klocek systemu.

Takie 2 głównie problemy jakie widzę, to raz, żeby nie skończyć z rozproszonymi monolitem czyli sytuacją kiedy każdy moduł/mikroserwis ściśle zależy od całej reszty i muszą być wdrażane jednocześnie, a 2 żeby też nie zrobić za dużych modułów/mikroserwisów, które ciężko ogarnąć i rozwijać. Jak to wyważyć?

Są jakieś wskaźniki, których sposobu liczenia nie pamiętam :) Natomiast pytania jakie warto sobie zadać, to "za co odpowiada ta usługa", "co się stanie jeżeli usługa ulegnie awarii", "co może wymusić konieczność zmiany tej usługi"

W ogóle czy jeden moduł/mikroserwis powinien ciągnąć dane z drugiego?

Zwykle trochę musi. Pytanie jak często i co się stanie jak nie będzie mógł tego zrobić. Jeżeli masz ten przykład wyżej, to usługa fakturowania, będzie musiała pobrać dane kontrahenta z innej usługi podczas tworzenia nowej faktury. Ale jeżeli faktura zostanie zapisana jako kompletny dokument, to nie będzie takiej potrzeby podczas odczytu faktury. Nie będzie również takiej potrzeby, jeżeli wyślesz całą fakturę do klocka odpowiedzialnego za eksport danych do księgowości, to on również nie będzie potrzebował odpytywać usługi "klienci" o dane. Zdecydowanie warto się pozbyć myślenia o mikroserwisach jako CRUDach odpowiedzialnych za konkretne typy obiektów.

Czy komunikacja synchroniczna nie powinna być opcjonalna i komponenty nie powinny się komunikować tylko eventami czy czymś podobnym?

Zależy. Nie da się zwykle zrobić wszystkiego asynchronicznie (albo jest to bardzo kosztowne). Jeżeli masz OPA, to bardzo ciężko będzie w sposób nieuciążliwy dla użytkownika zamienić całą komunikację na asynchroniczną. Natomiast, wciąż bardzo dobrym pomysłem jest np. przyjęcie intencji wystawienia faktury, zwrócenie informacji, że "faktura zostanie wystawiona i wysłana", a cała reszta procesu może się odbywać asynchronicznie. Natomiast domyślnie komunikacja asynchroniczna ma przewagę i powinna być punktem wyjścia.

5
anonimowy napisał(a):

Czyli w skrócie, według Ciebie @Riddle mikroserwisy nie mogą się ze sobą komunikować w żaden sposób (bo jeśli wyłączymy jeden to drugi ma działać identycznie jak działał)?

Myślę, że patrzycie na to w zbyt czarno-biały sposób. Zależność zależności nierówna. Jeżeli zachodzi jakakolwiek komunikacja to oczywiście zależność istnieje. Bo gdyby nie było zależności to faktycznie oznaczałoby to że serwis A musiałby działać identycznie niezależnie od działania / niedziałania serwisu B a zatem B byłby w ogóle niepotrzebny.

W mikroserwisach (i w ogóle wszelkich modułach) chodzi o to aby ta zależność dotyczyła możliwie małego obszaru modułu - czyli ma być możliwie proste API ale duża swoboda w kształtowaniu implementacji. Ważne jest aby API projektować świadomie a nie na zasadzie że wystawiamy wszystko co mamy i moduły grzebią sobie nawzajem w bebechach. Jeżeli świadomie uczynimy z id użytkownika część API to jest to ok. Oczywiście jest to jakaś zależność ale jednak słabsza zależność niż zależność od np. całej struktury opisującej użytkownika. Bo serwis/moduł zarządzający użytkownikami nadal ma swobodę w tym jak np. przechowuje profile i co w nich trzyma i wystawienie id w niczym nie przeszkadza. Wtedy jednak dobrze udokumentować zachowania związane z id - np. czy identyfikatory mogą być powtórnie używane, jaki jest ich zakres itp.

Teraz kwestia czy używać mikroserwisów czy nie, to trochę inny problem. Dość sceptycznie podchodzę do kwestii skalowania - monolityczna aplikacja może się dobrze skalować tak samo jak mikroserwis - serio nieużywanego kodu po prostu można nie ładować do pamięci, a naprawdę kilka, kilkadziesiąt MB więcej kodu na dysku to obecnie nie jest problem. Bardziej patrzyłbym przez pryzmat wygody konfiguracji, zwłaszcza jeśli mamy pewne elementy bezstanowe. Elementy bezstanowe będzie skalować łatwiej, więc może warto je oddzielić od tych stanowych.

Mikroserwisy warto rozważać jeśli:

  • moduły są tak mocno niezależne że chcemy je rozwijać różnymi zespołami, o różnych kompetencjach, i zespoły te mogą pracować przez większość czasu samodzielnie, tj. nie muszą robić codziennie 2h spotkania aby się dogadać
  • moduły chcemy napisać w różnych technologiach lub przynajmniej zostawić sobie taką furtkę na przyszłość
  • moduły nie korzystają ze wspólnych danych, a do tego dane własne modułów powinny być modelowane w inny sposób - np. nie ma sensu używać RDBMSa do przechowywania plików, albo NoSQL tam gdzie potrzebujemy ACID
  • moduły będą uruchamiane w różnych miejscach - np. chcemy je rozdzielić geograficznie ze względu na opóźnienia - klasyczny przykład: wszelkie rodzaje cache, które muszą być blisko użytkownika
  • moduły będą aktualizowane niezależnie od siebie, mają różnej długości cykl wydawniczy
  • moduły mają inne wymagania dotyczące bezpieczeństwa - np. chcemy zminimalizować obszar systemu który ma dostęp do wrażliwych danych - np. raczej nie chcemy aby moduł odpowiedzialny za reklamy miał możliwość grzebania w profilach użytkowników... choć może jak jesteśmy Googlen albo FB to właśnie chcemy xD
  • moduły mają mocno różne wymagania wydajnościowe - dobrym przykładem jest streaming, gdzie obszar aplikacji odpowiedzialny za składowanie i dostarczanie filmów ma drastycznie inne zapotrzebowanie na przestrzeń dyskową oraz użycie sieci niż obszar aplikacji zarządzający profilami

Bardziej myślałbym o mikroserwisach jak o częściach w samochodzie. Każda część może być dostarczona przez innego dostawcę, części mają często proste interfejsy a skomplikowaną logikę w środku, mogą być z różnych materiałów, implementacja może być mocno zmieniana tak długo jak spełnione są wymogi zewnętrzne. Jednak te części ze sobą współpracują i nie musi być wcale tak że jak jedna część przestaje działać to całość będzie działać poprawnie.

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