Jak dobrze podzielić aplikację na moduły

0

Powiedzmy, że mamy dwa moduły: Ordering i Products (wiem, oklepana domena, ale po co szukać innej, jak nawet tej się nie umie dobrze zrobić...).

Moduł Ordering potrzebuje danych z modułu Products do jakiejś swojej logiki biznesowej (np. nie można dodać nieaktywnego produktu do zamówienia). Standardowym rozwiązaniem jest w takim przypadku emitowanie z modułu Products eventu zawierającego ProductId i IsActive. Moduł Ordering nasłuchuje na ten event, aktualizuje swoje dane i wszystko ładnie działa - nie musi odpytywać modułu Products, żeby sprawdzić, czy produkt jest aktywny.

Po jakimś czasie pojawia się kolejne wymaganie: jeśli zamówienie zawiera produkt z grupy lub podgrupy X, wtedy zamówienie nie może zostać normalnie kontynuowane, tylko osoba Y musi dodatkowo zrobić Z. Żeby nie odpytywać modułu Products, moduł Ordering zawiera teraz dodatkowo hierarchię grup produktów aktualizowaną przez eventy z modułu Products.

Czas mija i pojawia się kolejne wymaganie. Otóż produkt może mieć coś takiego jak dopłaty (np. przedłużenie jakiegoś tam kabla wchodzącego w skład produktu). W Ordering chcemy, żeby można było takie dopłaty wiązać z pozycjami zamówień. A czego potrzebujemy, by sprawdzić, czy daną dopłatę można powiązać z danym produktem? Oczywiście danych z modułu Products.

Już chyba wiadomo, do czego zmierzam: wraz ze wzrostem złożoności systemu moduły potrzebują coraz więcej swoich wewnętrznych danych. Możemy te dane kopiować, tylko czy ma to sens? Czy Ordering ma mieć wszystkie dane z Products skopiowane u siebie, żeby móc pełnić swoją funkcję? Co na ten temat sądzicie? Niezależność modułów to fikcja?

9

Moduł Ordering nasłuchuje na ten event, aktualizuje swoje dane i wszystko ładnie działa - nie musi odpytywać modułu Products, żeby sprawdzić, czy produkt jest aktywny.

Czyli co w Module Ordering chcesz robić kopię bazy Products?
Bez sensu!

I jakie eventy? Po co to? Informacja o produkcie niech będzie statyczna w bazie danych, a podczas tworzenia zamówienia moduł Ordering sam odpyta moduł Products o dostępność i ewentualne możliwe kombinacje produktów. Po co na próżno mają jakieś eventy latać i po co robić redundancje informacji.
Generalnie mieszasz funkcjonalności modułów ze strukturami danych na których te moduły pracują.

Czy Ordering ma mieć wszystkie dane z Products skopiowane u siebie, żeby móc pełnić swoją funkcję? Co na ten temat sądzicie? Niezależność modułów to fikcja?
Nie na tym polega modułowość, żeby każdy moduł był samodzielną aplikacją.

1

@nobody01: A czym jest moduł? Moduł: must be independent and interchangeable and must have everything necessary to provide desired functionality?

Oczywiście, że ma być niezależny i musi mieć wszystko co potrzebne ale Ty zakładasz, że sprzedawca zestawu kluczy do samochodu ma dodać Ci do tego zestawu samochód bo przecież bez samochodu nie będziesz miał nad czym tymi kluczami pracować.

Moduł zamówień z samymi produktami może być powiązany jedynie jakimiś identyfikatorami i tyle... Zamówienie to lista identyfikatorów produktów a nie całe produkty skopiowane do zamówienia.

0

To po co w takim razie są te wszystkie eventy, brokery, skoro i tak koniec końców moduły / mikroserwisy są od siebie zależne? Żeby móc wykonać jakąś operację asynchronicznie? Tylko po to?

0

@nobody01: struktura i sposób poukładania danych w systemie to jedno, komunikacja pomiędzy pracującymi modułami to drugia, a asynchroniczne wykonywanie operacji to rzecz trzecia i są to narzędzia / składniki systemu, które poukładane i wykorzystywane w odpowiedni oraz zaplanowany sposób tworzą spójny działający system.

Eventy to eventy a dane to dane. Oczywiście wraz z eventem mogą być przesyłane dane ale to jakie dane ile i po co wynika z architektury i budowy całego systemu.

0

To jest trochę pytanie, co wybrać: direct call czy messaging do komunikacji. Sens wydaje się mieć używanie direct call do pobierania danych (np. moduł B potrzebuje danych z modułu A, żeby zrealizować jakiś swój wewnętrzny proces, więc odpytuje API modułu A) i messaging, gdy np. jakiś proces z modułu B wymaga zainicjowania innego procesu w module A (moduł B wysyła event, na który odpowiada moduł A).

3
nobody01 napisał(a):

To po co w takim razie są te wszystkie eventy, brokery, skoro i tak koniec końców moduły / mikroserwisy są od siebie zależne? Żeby móc wykonać jakąś operację asynchronicznie? Tylko po to?

Ja to widzę tak, że gdy już użytkownik wybierze produkty i za nie zapłaci, to moduł Ordering wysyła event OrderCompleted, a event ten jest odbierany przez: Invoicing do wystawienia faktury, Delivery do spakowania i wysłania i DataAnalytics do zebrania informacji o najczęściej kupowanych razem produktach i skuteczniejszych podpowiedzi w przyszłości, oraz inne moduły.

No, ale ja nie jestem architektem, nie znam się na kopiowaniu baz przez eventy, może tak trzeba. Ale ja jednak wolę mieć jedno źródło prawdy.

0

@somekind: W ogóle żadnych danych bys nie kopiował przez eventy?

5

nobody01 przede wszystkim podejście zależy od tego czy to jest modularny monolit (i wtedy dane lecą często w RAMie przez jakiś EventBus) czy to są inne aplikacje (czyli moduł = aplikacja)

0

@ProgScibi: A w tym drugim przypadku jak byś postąpił?

0
ProgScibi napisał(a):

nobody01 przede wszystkim podejście zależy od tego czy to jest modularny monolit (i wtedy dane lecą często w RAMie przez jakiś EventBus) czy to są inne aplikacje (czyli moduł = aplikacja)

To fakt. W przypadku różnych aplikacji takie eventy z danymi mają większe uzasadnienie niż w przypadku systemu zintegrowanego "monolitu".
Popularnym przykładem są np. integracje systemów CRM z FK gdzie dane do faktury zwykle do systemu FK wysyła się kompletne czyli pełne dane klienta, pełne opisy produktów. Chociażby tylko dlatego, że temu drugiemu systemowi identyfikatory produktów czy kontrahentów z systemu pierwszego nic nie mówią, a bez sensu zadawać 2 pytania czyli
MESSAGE/EVENT Z CRM -> FK, że jest nowa FK
a następnie żądanie od FK do CRM o pełne dane kontrahenta i produktów. Czasem łatwiej będzie zrobić to wszystko w pierwszym kroku.

3
nobody01 napisał(a):

@somekind: W ogóle żadnych danych bys nie kopiował przez eventy?

Kopiowałbym w dwóch przypadkach: gdybym tego potrzebował, i gdyby mi za to płacili.

0

@somekind: Co to znaczy, że wtedy, gdy byś potrzebował? Mógłbyś podać jakiś przykład? W moim przypadku też pozornie potrzebuje, ale jednak każdy tu pisze, żeby nie kopiować.

@katakrowa: Czyli wysylalbys dane w eventach, gdy moduły są luźno powiązane ze soba? W Twoim przypadku wystarczy wysłać tylko jeden event z danymi z CRMa, aby FV mógł pracować bez odpytywania CRMa.

1

Załóżmy, że to co opisujesz jako moduły, to mikroserwisy systemu.

nobody01 napisał(a):

Moduł Ordering potrzebuje danych z modułu Products do jakiejś swojej logiki biznesowej (np. nie można dodać nieaktywnego produktu do zamówienia). Standardowym rozwiązaniem jest w takim przypadku emitowanie z modułu Products eventu zawierającego ProductId i IsActive. Moduł Ordering nasłuchuje na ten event, aktualizuje swoje dane i wszystko ładnie działa - nie musi odpytywać modułu Products, żeby sprawdzić, czy produkt jest aktywny.

Pytanie w czym przeszkadza ci odpytanie Products? Jeżeli jest to jedynie prosta informacja na temat stanu, to na ogół w niczym, a dołożenie do tego kolejnego miejsca w którym przechowywane są dane powoduje zwiększenie kosztów wytworzenia i utrzymania systemu. Musisz miec miejsce pod kolejną bazę danych, zajmować się jej serwisowaniem. Kolejka może ci paść i dane przestana przepływać. Można z tym walczyć, ale to znowu jest praca.

Po jakimś czasie pojawia się kolejne wymaganie: jeśli zamówienie zawiera produkt z grupy lub podgrupy X, wtedy zamówienie nie może zostać normalnie kontynuowane, tylko osoba Y musi dodatkowo zrobić Z. Żeby nie odpytywać modułu Products, moduł Ordering zawiera teraz dodatkowo hierarchię grup produktów aktualizowaną przez eventy z modułu Products.

O... właśnie. Podatność na rozwój spada wraz z wielkością systemu.

Czas mija i pojawia się kolejne wymaganie. Otóż produkt może mieć coś takiego jak dopłaty (np. przedłużenie jakiegoś tam kabla wchodzącego w skład produktu). W Ordering chcemy, żeby można było takie dopłaty wiązać z pozycjami zamówień. A czego potrzebujemy, by sprawdzić, czy daną dopłatę można powiązać z danym produktem? Oczywiście danych z modułu Products.

I prościej jest mieć ten moduł products z API, które może zostać rozszerzone wraz z rozwojem systemu, niż zastanawiać się, czy jak zmienisz strukturę payload w evencie to wszystkie konsumujące to usługi padną.

Już chyba wiadomo, do czego zmierzam: wraz ze wzrostem złożoności systemu moduły potrzebują coraz więcej swoich wewnętrznych danych. Możemy te dane kopiować, tylko czy ma to sens? Czy Ordering ma mieć wszystkie dane z Products skopiowane u siebie, żeby móc pełnić swoją funkcję? Co na ten temat sądzicie? Niezależność modułów to fikcja?

Czy to ma sens, musisz sam ocenić. Są przypadki kiedy to ma sens i takie gdzie będzie to absurdem, bo nie realizuje to żadnego wymagania, jest bardziej skomplikowane, podatne na awarie. Podałeś szkolny przykład, który pokazuje co można zrobić, ale niekoniecznie jest w tym sens.

Załóżmy, że mamy coś odrobinę bardziej z życia, jakiś sklep komputerowy, ma oddziały stacjonarne, sklep internetowy. Chcesz, żeby w sklepie internetowym była widoczna dostępność jakiegoś produktu. Magazyn to zbiór rekordów przyjęciach i wydaniach produktów. Wielu ludzi przegląda witrynę sklepu, ale zamawiają znacznie rzadziej. Czyli musiałbyś w tym wypadku, ileś razy na sekundę odpytywać magazyn o stany różnych produktów, a ten przeglądałby wszystkie rekordy w DB, żeby policzyć czy coś jest, czy NIE MA na magazynie. Nie możesz sobie tego cache'ować po stronie sklepu, bo to nie on odpowiada za przyjęcia towarów i część wydań (sklepy stacjonarne). Odpytywać tez nie możesz, bo event sourcing nie wyrobi przy takim ruchu. Ponieważ masz dużo większy ruch na czytaniu, niż na zmianach, potrzebujesz jedynie drobnej części informacji (stanu obecnego) a nie historii, możesz zastosować jakiś event bus do powiadamiania wszystkich zainteresowanych o miejscu gdzie coś się zmieniło, lub nowych wartościach.
Czyli:
Sklep ma własną, ulotną bazę danych (np. Redis), która ma listę produktów ze stanami. Buduje ją sobie przy starcie korzystając z danych z Magazynu (odpytuje przez jakieś API). W momencie zmiany stanu, Magazyn emituje sygnał zawierający id zmienionego produktu, typ zmiany (zmiana ceny, zwiększenie stanu, zmniejszenie stanu, sklep odbiera sygnał, jeżeli zmiana jest dla niego istotna (cena, zmniejszenie stanu) odpytuje magazyn o nowe wartości. Możesz też wrzucić do sygnału nowy stan produktu (cena, stan magazynowy itd.) i wtedy nie trzeba juz pytać, wystarczy przetworzyć stan. W praktyce tak czy inaczej, byłby to pewnie osobny serwis odpowiedzialny wyłącznie za przechowywanie aktualnego stanu produktów i obsługę sygnałów o zmianach, udostępniający te dane na żądanie ze sklepu.

1
nobody01 napisał(a):

@katakrowa: Czyli wysylalbys dane w eventach, gdy moduły są luźno powiązane ze soba? W Twoim przypadku wystarczy wysłać tylko jeden event z danymi z CRMa, aby FV mógł pracować bez odpytywania CRMa.

"Luźno" to pojęcie niejasne. Zrobiłbym tak gdyby:

  • Moduł FK nie miał innej możliwości by dostać się do bazy CRM ;
  • Była sytuacja, w której te dane do faktury są niezbędne w FV i sama informacja o zmianie jej stanu w CRM nic nie wnosi dla systemu FK ;

Niezależnie od powyższych rozważań zrobiłbym:

  • API do pobierania listy faktur ;
  • API do pobierania danych pojedynczej faktury ;
  • mechanizm callback/eventów dla systemów zewnętrznych informujący o pojawieniu się lub zmianie statusu faktury w systemie.

Taki zestaw narzędzi można potem używać w ramach własnego zintegrowanego systemu jak i podczas integracji z zewnętrznymi.

2

Obejrzyj , odkrywanie granic nie jest trywialne, dużo zależy od Twojej domeny

2

Zostałem wywołany przez @nobody01 więc się stawiam. Tutaj jest kilka problemów i możliwych rozwiązań (co by nie było zbyt łatwo ;)).

Najpierw to o czym pisał katakrowa w pierwszym poście, a więc kwestia tego czy modułowość == oddzielne bazy. Po części się z nim zgadzam co do wątpliwości, ale nie zgadzam się z wnioskiem- mianowicie z tym że powinno się używać jednego "źródła prawdy" oraz nie należy robić "kopii bazy". O żadnej kopii całej bazy nie ma mowy, ale trzeba sobie również odpowiedzieć czy chcemy iść w bardziej pragmatyczne podejście czy też nie- co powinno zależeć od złożoności systemu.

Do tego dochodzi to czy przez moduły rozumiemy jeden proces ale logicznie rozbity na poszczególne moduły (modularny monolit), czy też system rozproszony (mikroserwisy).

Ja wypowiem się w kontekście systemu gdzie każdy moduł ma swoją bazę danych, a to czy jest to system rozproszony czy nie to już sprawa drugorzędna. Rozwiązania jakie na szybo przychodzą mi do głowy:

1. Pełna asynchroniczność i każdy moduł budujący dane których potrzebuje
W tym przypadku moduł Ordering nasłuchiwałby eventów z Products i tworzył własną kopię, wliczając w to dane z produktów. Oczywiście te dane nadal powinny być ograniczone, a więc zawierać tylko to co potrzebne do wyegzekwowania zasad biznesowych. Co ważne, kształt tych danych może być całkowicie inny niż w module z którego pochodzą. Czyli np. dla każdego produktu Ordering może przechowywać jeden dokument JSON (bazy NoSQL) który zawierał będzie wszystkie informacje jakie Ordering potrzebuje, w tym podgrupy, tagi, dostępne rozszerzenia itp.

Zalety

  • Proces zamówienia jest ograniczony do małej liczby modułów, ponieważ większość danych potrzebnych dla zasad biznesowych jest już dostępna

  • Duplikowanie i denormalizacja danych to dziś już żadna nowość, a w systemach rozproszonych wręcz standard (mimo wszystko umieszczę to również w wadach). Tworzymy różne widoki tych samych danych na konkretne potrzeby.

  • Proces jest wykonywany w ograniczonym kontekście (moduł Ordering) przez co jest łatwiej kontrolowany

    Wady

  • Denormalizacja danych (zaleta powyżej)

  • Eventual consistency

  • Potencjalne wyciekanie wiedzy domenowej z jednego modułu do innych (ale ryzyko również występuje przy innych podejściach, jak pokazane niżej)

2. Pełna asynchroniczność, każdy moduł ograniczający dostęp do swoich danych, zaangażowanie dłuższego procesu
Rozbijamy konkretny przypadek na większy proces i angażujemy w niego te moduły które potrzeba. W tym przypadku moduł Ordering zasygnalizowałby eventem że składanie zamówienia się zaczęło, ale to moduł Products odpowiedzialny by był za walidacje danych dotyczących produktu. Wynik tej walidacji były również eventem (sukces lub porażka) którego nasłuchiwałby moduł Ordering i na tej podstawie zamówienie zakończyło by się powodzeniem lub błędem. Mamy tutaj podejście event-driven gdzie zdarzenie w jednym module aktywuje zdarzenie w innym.

Zalety

  • Większość danych i zasad biznesowych ograniczone do konkretnych modułów

  • Brak eventual consistency- moduł Products będzie miał dostęp do aktualnych danych

    Wady

  • Nadal mamy ryzyko wycieku wiedzy domenowej tylko w odwrotnym kierunku- teraz to moduł Products musi wiedzieć i reagować na jakieś zewnętrzne procesy wymagające walidacji produktów

  • Musimy angażować pełnoprawny proces tam gdzie możemy mieć coś stosunkowo trywialnego

  • Koordynacja procesu- tutaj z pomocą może przyjść orkiestracja (ang. orchestration) ale nadal jest to dodatkowa warstwa skomplikowania

3. Synchroniczny proces, każdy moduł ograniczający dostęp do swoich danych
W tym przypadku poszczególnie serwisy odpytywane są za pomocą synchronicznej komunikacji (np. HTTP). Ordering może wysłać polecenie do Products w celu walidacji produktów, lub na odwrót- spytać Products o zwrócenie zasad i wykonanie walidacji po stronie Ordering.

Zalety

  • Większość danych i zasad biznesowych (to niekoniecznie) ograniczone do konkretnych modułów

  • W pełni łatwo kontrolowany proces

  • Brak eventual consistency

    Wady

  • Tight coupling między modułami- Ordering wie że musi odpytać Products o coś, w przypadku błędu requestu cała operacja nam siada

  • Nie unikamy wycieku wiedzy domenowej ze względu na punk powyżej

  • Ryzyko synchronicznego angażowania wielu modułów łańcuchem wywołań (moduł A odpytuje B, który z kolei odpytuje C, który odpytuje D...) który prowadzi w zasadzie do spaghetti na poziomie architektury

Dodatkowo do powyższych, spróbowałbym spojrzeć na problem "spoza pudełka". W pierwszej kolejności spytałbym czy egzekwowanie takich zasad jest w ogóle warte walidacji. Teoretycznie, klient nigdy nie powinien osiągnąć stanu w którym wysłałby np. request z produktami posiadającymi niepoprawne rozszerzenia. Jeśli do czegoś takiego by doszło to coś nam siadło już wcześniej, albo ktoś umyślnie powoduje błąd. Tak czy inaczej będziemy mieli błąd, na tym czy innym poziomie. Koniec końców będzie to wymagać interwencji supportu. Często bywa tak że lepiej przepuścić błędne zamówienie, i rozwiązań problem na poziomie biznesowym. Tak więc pojawiają się pytania natury biznesowej, a nie tylko technicznej.

Załóżmy jednak że faktycznie przepuszczenie czegoś takiego przez system nie może mieć miejsca. Wtedy ja proponuję jedno z powyższych rozwiązań. Problem wycieku wiedzy domenowej między Ordering a Products można częściowo rozwiązać wprowadzając dodatkowy moduł odpowiadający za szerszą walidację zasad. Wtedy ten moduł będzie posiadał potrzebne dane z Products, i reagował na to co dzieje się w Ordering. Owszem, ktoś może stwierdzić że i tutaj mamy przecież wyciek wiedzy domenowej- ale tym razem mamy od tego przeznaczony specjalny moduł, a nie moduły które "przypadkowo" muszą obsłużyć coś co do nich nie należy (nie jest w ich gestii odpowiedzialności).

4

Moim zdaniem, błędem jest patrzenie na projekt wyłącznie przez pryzmat tego jak łatwo będzie to zaprogramować i czy będzie to zgodne z jakimś tam losowo wybranym wzorcem. System ma realizować jakąś tam funkcjonalność biznesową, a nie startować w konkursie na miss jakiejś tam architektury. Wybór technologii, struktury, sposobu komunikacji pomiędzy modułami, sposobu przechowywania danych zależy od tego na co kładziemy większy nacisk i wiąże się zawsze z jakimiś kompromisami. Jak chcemy szybko pisać, to pewnie odbije się to na skalowalności i łatwości utrzymania systemu. Jak zależy nam na niezawodności, to pewnie spadnie dostępność itd.

Warto popatrzeć sobie na AWS well architectured framework i jego kryteria :

  • Operational excellency (system jest łatwy w rozwoju i robi to co ma robić)
  • Security - spójność danych, limitowanie dostępu itd.
  • Reliability - brak SPoF, backupy, monitoring
  • Performance - efektywne dobranie rozwiązań do potrzeb, czy wybrać SQL, czy NoSQL, kontenery, czy VM itd.
  • Cost optimization - czy to wszystko wypisane wyżej nie kosztuje niepotrzebnie dużo.

Postrzeganie systemu wyłącznie przez jeden z tych wskaźników jest błędem, bo możemy sobie np. zbudować łatwy i szybki w rozwoju system, ale co z tego jak trzeba będzie wyskalować infrastrukturę, do poziomu przy którym nie będzie nas stać na jej utrzymanie, albo wszystko fajnie, ale nie da się przypisać ról użytkownikom.

0

Na wstępie dziękuję za tak rozbudowane odpowiedzi, @piotrpo i sczególnie @Aventus :)

Ten fragment:

Ryzyko synchronicznego angażowania wielu modułów łańcuchem wywołań (moduł A odpytuje B, który z kolei odpytuje C, który odpytuje D...) który prowadzi w zasadzie do spaghetti na poziomie architektury

Właśnie czegoś takiego chciałbym uniknąć. Czy ma sens mieszanie podejść? Np. moduł Ordering ma kopię tych danych z modułu Products, których najczęściej potrzebuje, a te dane, które potrzebuje rzadziej, pobiera bezpośrednio z API modułu Products?

3

Ma sens, bo być może możesz sobie pozwolić na odczyty z cache (własnej kopii danych, która może nie być spójna w danym momencie), natomiast przy jakimś zapisie musisz mieć świeże dane, aby przeprowadzić walidację.

2

@nobody01: oczywiście że to może mieć sens, wszystko zależy od konkretnego przypadku. Ja akurat jest raczej przeciwnikiem bezpośredniej komunikacji HTTP pomiędzy serwisami i stawiam na podejście event-driven ale nie oznacza to że ślepo stosował bym takie podejście do wszystkiego.

1

@Aventus: Tak jak piszesz, zależy co jest potrzebne i jaki problem chce się rozwiązać. Jakie masz powody przeciwko komunikacji przez http (czy inne protobuffy), generalnie pobieranie danych na żądanie?

1

@piotrpo: podstawowym powodem jest wspomniany przeze mnie wyżej możliwy łańcuch wywołań. No bo nie ma żadnej gwarancji że wysłanie jednego requestu nie skończy się kilkoma requestami, kolejno do dalszych serwisów. Pomijając to że mamy wiele możliwych punktów błędu, zwiększony czas obsługi procesu, to jeszcze siłą rzeczy moduły będące bezpośrednimi klientami innych modułów muszą wiedzieć o ich istnieniu. Ogólnie jest to podejście raczej odradzane.

Do tego dochodzi również łatwość skalowalności. Wiem że można wystawiać API za load balancerami, używać kontenerów itp. W każdym przypadku jednak *competing consumers * nasluchujacy eventów (lub commands) to zwyczajnie łatwiejsze podejście. A przynajmniej tak wynika z mojego doświadczenia.

0

@Aventus: Nie wiem czy dobrze rozumiem, dla uproszczenia załóżmy, że mamy jakiegoś klienta, koszyk, katalog produktów, koszyk ma zwrócić przechowywaną przez siebie liczbę wrzuconych produktów i jakieś ich dane z katalogu.

  • klient żąda od koszyka danych
  • koszyk wrzuca na event bus prośbę o dane konkretnych produktów i czeka z wysłaniem odpowiedzi http
  • katalog odbiera żądanie, czyta sobie z bazy danych potrzebne informacje, wrzuca na event bus zwrotkę
  • koszyk odbiera informację i ostatecznie odpowiada klientowi

Dziwność tego rozwiązania bierze się z mojego niezrozumienia, czy nie pasującego do takiego modelu przykładu?

Rozumiem, oczywiście, że asynchroniczność jest generalnie fajniejsza niż synchroniczność i jeżeli np. klient coś zamówi, to koszyk wyśle komendę realizacji do usługi zamówienia, która pobierze sobie to kiedyś z kolejki i obsłuży, ale jeżeli masz na wejściu synchroniczne żądanie, to gromadzenie danych do jego obsługi asynchronicznie wydaje mi się trochę na siłę, bo całą tą asynchroniczność trzeba zamknąć w ramach synchronicznego żądania.

---edit
Chyba, że zakładamy, że koszyk zawsze posiada w cache/kopii dostatecznie dobre dane (eventual consistency), żeby obsłużyć żądanie, które dostaje od klienta.

0

@piotrpo: tutaj chyba nie bardzo pasuje przykład. A przynajmniej ja w serwisie Koszyk widział bym coś znacznie bardziej prostego i po prostu przechowywał tam informacje o aktualnym stanie koszyków, a więc w nich IDs produktów. Jeśli trzeba by również zachować dodatkowe informacje takie jak cena czy nazwa produktu, to byłaby to bardziej obsługa pewnego widoku, a więc te dane mogłyby pochodzić nawet od klienta UI, który już wcześniej je otrzymał wraz z IDs produktów.

to gromadzenie danych do jego obsługi asynchronicznie wydaje mi się trochę na siłę

To prawda, jeśli mowa o gromadzeniu "w locie", tzn. takie gdzie trzeba zwrócić dane na potrzeby konkretnej, aktualnie wykonywanej operacji. Od tego właśnie jest wcześniejsze budowanie danych pochodzących z innych serwisów, ale na potrzeby konkretnego serwisu. W przeciwnym razie masz słuszne wątpliwości, bo bawimy się w zastępowanie asnychronicznoścą odpytywania poprzez HTTP. Sens asynchroniczności jest taki aby wykonywać różne operacje, ale poszczególne moduły już mają gotowe dane do obsługi tych operacji.

5

@piotrpo: A propos wad komunikacji synchronicznej to polecam obejrzeć prezentację

0

Powiedzmy, że chcemy aby Ordering i Products komunikowały się ze sobą asynchronicznie.

W module Products mamy hierarchię grup produktów. Każda grupa produktów ma jakieś swoje cechy (np. produkty z grupy X nie są kupowane od zewnętrznej firmy, tylko wytwarzane przez nas). Jeśli grupa produktów ma cechę Y, to wszystkie produkty będące w tej grupie również mają domyślnie tę cechę. W module Ordering potrzebujemy wiedzieć, czy produkt Z jest wytwarzany przez nas, czy kupowany od kogoś innego po to, żeby np. wyliczyć rabat.

Moje rozumowanie jest takie: moduł Ordering nie potrzebuje wiedzieć nic o grupach produktów. Jego nie interesuje, w jakiej grupie jest produkt. Interesuje go tylko, czy produkt jest wytwarzany przez nas. W Ordering powinniśmy więc jedynie przechowywać przy produkcie flagę, czy produkt jest wytwarzany przez nas, czy nie. I tu pytanie, jak to zrobić. Jeśli ktoś w Products zmieni dane grupy produktów i okaże się, że produkty z grupy Z również są teraz wytwarzane przez nas, to jak powiadomić o zmianach moduł Ordering? Po aktualizacji danych grupy pobrać wszystkie produkty, które są teraz w tej grupie, i wysłać event zawierający zmienianą cechę i identyfikatory wszystkich produktów, które mają teraz tę cechę? Ordering w zależności od tego, jaka cecha została zmieniona, zaktualizuje swoją bazę produktów lub nie. Dobrze myślę?

1

Masz moduł Products, który odpowiada za przechowywanie całości danych o produktach. To jak je sobie przechowuje jest jego wewnętrzną sprawą, a nikt na zewnątrz nie musi i nie powinien być świadomy relacji pomiędzy produktem a grupą i reguł biznesowych wykonywanych w momencie zmian wykonanych na poziomie grupy, Czyli:

  • Masz grupę nasze w niej 5000 produktów. W praktyce ta grupa jest jedynie narzędziem ułatwiającym zarządzanie produktami.
  • Dodajesz jakąś wartość domyślną typu kolor = różowy do grupy
  • Zmieniasz w ten sposób stan tych 5000 produktów i musisz o tym poinformować resztę systemu, więc emitujesz 5000 nowych stanów po jednym dla każdego produktu. Stan zawiera wszystkie informacje o produkcie i jest rozgłaszany przez jakiś service bus w topicu product-updates
  • Moduł Ordering jest zainteresowany częścią danych produktu, subskrybuje topic product-updates, bierze interesujące go dane i zapisuje lokalnie w dogodnej dla siebie formie (np. key-value jeżeli wyszukiwanie zawsze będzie po indeksie). Oczywiście zapisuje tylko te dane, które są mu potrzebne.

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