Aplikacja z podziałem na moduły

1

Zna ktoś jakąś aplikację open-source podzieloną na moduły? Mówi się na taką architekturę chyba architecture by feature. Chodzi mi o to, że mam np. projekt Modules.Customers i w nim wszystkie związane z klientami DTO, serwisy, komendy, zapytania, handlery itp.

1

Tutaj masz całkiem spory projekt. Możesz też spróbować kontrybucji bo community jest dość pomocne.
https://github.com/OrchardCMS

1

Cos takiego najlepiej obrazuja aplikacje stosujace Domain Driven Design i/lub oparte o mikroserwisy. Tutaj przyklad sklepu online w oparciu wlasnie o mikroserwisy, z "lekkim" zastosowaniem DDD

Jest to aplikacja przykladowa, z zalaczona darmowa ksiazka ktora warto przeczytac. Warto jednak zaznaczyc ze modularnosc mozna osiagnac bez stosowania mikroserwisow.

0

A jak można by coś takiego osiągnąć bez stosowania mikroserwisów? Myślę o czymś takim:
Api (głównie kontrolery)
Modules:
-Modules.Customers
-Modules.Auth
-Modules.Email
Database (DbContext, konfiguracje mapowań na tabele, migracje)
Infrastructure (klasy niepasujące do pozostałych, np. jakieś extension methods)

2

W takim przypadku jest dokladnie tak jak piszesz. Ja bym nie nazywal projektow Modules.X a stosowal pelnoprawne, domenowe nazewnictwo. Chodzi o stosowanie DDD, nawet "lekkie" DDD na mala skale. No ale to moje zdanie. Skoro chcialbys dokonac takiego podzialu, to kazdy modul powinien posiadac swoje wlasne dane -czyli albo oddzielna baza, albo schema na kazdy modul. Teraz kluczem do komunikacji miedzy Twoimi modulami, oraz miedzy kontrolerami a modulami powinno byc zastosowanie command-handler, oraz co za tym idzie CQRS. Mozesz to osiagnac np. stosujac wzorzoec mediator (bilbioteka MediatR lub wlasne rozwiazanie). Wtedy np. kontrolery staja sie "glupie", nie interesuje je co i jak jest wykonywane. Wysylaja jakies polecenie i otrzymuja wynik (lub nie- brak wyjatku moze byc wynikiem poprawnej operacji). Na przyklad:

public IActionResult RegisterCustomer(RegisterCustomerViewModel model)
{
    _mediator.Send(new RegisterCustomer(model.Username, model.Password, model.Email));
   return View(blah);
}
1

Tu masz aplikację korzystającą z MediatR. https://github.com/JasonGT/NorthwindTraders
Ale nie nazwałbym raczej tego modułami.

0
Aventus napisał(a):

Ja bym nie nazywal projektow Modules.X a stosowal pelnoprawne, domenowe nazewnictwo.

Czyli np. MyProject.Customers?

Skoro chcialbys dokonac takiego podzialu

Sugerujesz, że nie warto przy monolitycznej aplikacji? Ogólnie dotychczas używałem podziału zaproponowanego przez @somekind (Api, Application, Contracts, Model) i pisało się przyjemnie. Problem z tym, że czasem trudno jest nawigować po projektach, gdy urosną (tzn. nie mam żadnego dużego projektu, po prostu się domyślam).

kazdy modul powinien posiadac swoje wlasne dane -czyli albo oddzielna baza, albo schema na kazdy modul.

Czyli to oznacza jeden DbContext na moduł?

Teraz kluczem do komunikacji miedzy Twoimi modulami, oraz miedzy kontrolerami a modulami powinno byc zastosowanie command-handler, oraz co za tym idzie CQRS.

MediatR już stosuję ;)

1

Czyli np. MyProject.Customers?

Tak.

Sugerujesz, że nie warto przy monolitycznej aplikacji?

Nie, wręcz przeciwnie. Sam aktualnie piszę podobnie rozbitą aplikację.

Czyli to oznacza jeden DbContext na moduł?

To już zależy jak sobie to zaplanujesz. Możesz np. wyabstrahowac repozytoria dla każdego modułu, a mieć jeden kontekst w implementacjach. Przy założeniu że będzie korzystał z jednej bazy danych, logicznie rozbitej na moduły przy użyciu schemas.

0

Jeszcze takie pytanie: czy w tych modułach powinny znajdować się też interfejsy, czy tylko ich implementacje? Jeśli to drugie, to gdzie wrzucić te interfejsy?

1

Jeśli interfejsy są specyficzne dla danego modułu (kontekstu) to w tym module je umieść. Implementacje powinny znajdować się gdzieś indziej, np. w projekcie infrastruktury.

0

Jeszcze takie pytanie: w którym projekcie powinny być encje? Jeśli umieszczę je w osobnych modułach, to łatwo dostanę cykliczne referencje między projektami.

4

Zaznaczam ze mowiac moduly mam na mysli konteksty (bounded contexts). Kazdy modul powinien miec swoje dane- jak juz bylo wymienione- tak wiec i swoje encje. Czyli np. projekty Customers i Ordering beda mialy wlasne klasy reprezentujace klienta, w kazdym module oznaczajace co innego i posiadajace inne wlasciwosci. Byc moze beda nawet kryly sie pod inna nazwa (klient w Customers moze byc adresatem w Ordering). Moduly nie powinny sie ze soba bezposrednio komunikowac. Od tego jest integracja miedzy kontekstami za pomoca polecen (commands) i zdarzen (events, w MediatR kryjace sie pod notifications). Commands i Notifications mozesz umiescic w oddzielnych projektach, np. Messaging albo Integration. I tak np. project Customers bedzie mial odpowiadajacy temu projekt Customers.Integration. Inne moduly beda mialy referencje do projektu Customers.Integration, czyli tylko do commands i events. Same handlery umiesc w projektach modulowych.

0

Proponowanego overengineeringu odnośnie podziału na moduły nie skomentuje :), natomiast zastanawia mnie jedna rzecz, po co w zaproponowanej architekturze dawać możliwość jednemu modułowi wysyłania komend do innego modułu? jakiś przykładowy scenariusz? bo nie mogę sobie tego wyobrazić.

0

Overengineering bo? Chyba wyraźnie dyskusja dotyczy architektury w której cos takiego chce się stosować. Nie wnikam czy w tym konkretnym przypadku taka architektura ma sens czy nie, bo nie tego dotyczy dyskusja. Pytanie było o podział na moduły (modularny monolit) więc w tym kontekście odpowiedziałem. Ja nie proponuje aby stosować taki podział do napisania kalkulatora... Ale Ty oczywiście jesteś jednym z tych którzy z góry wiedzą o jakich wymaganiach mowa, i na wszystko mają złoty środek tak?

Po co wysyłanie komend? Po to żeby zapewnić loose coupling między modułami, wysyłać polecenia dokonania pewnych działań biznesowych (zarejestruj klienta, potwierdź/zaksięguj zamówienie) oraz komunikować zdarzenia (klient zarejestrowany, zamówienie zaksięgowane).

Proponuję poczytać o DDD, a konkretnie o bounded contexts, przechowywaniu danych dla każdego oraz komunikacji między kontekstami.

EDIT: A samą komunikację w oparciu o command/handler warto stosować nawet bez modularnosci, o czym pisałem już wcześniej.

1
Aventus napisał(a):

Overengineering bo? (...) Ale Ty oczywiście jesteś jednym z tych którzy z góry wiedzą o jakich wymaganiach mowa, i na wszystko mają złoty środek tak?

Żeby mieć modularny monolit nie jest potrzebny ani CQRS, ani mediator. Jest to jedna z opcji, ale jak zauważyłeś nie znamy wymagań, więc chyba najlogiczniej jest wtedy iść w minimalne rozwiązanie?

Aventus napisał(a):

Po co wysyłanie komend? Po to żeby zapewnić loose coupling między modułami, wysyłać polecenia dokonania pewnych działań biznesowych (zarejestruj klienta, potwierdź/zaksięguj zamówienie) oraz komunikować zdarzenia (klient zarejestrowany, zamówienie zaksięgowane).

Moje pytanie brzmiało po co wysyłać komendy pomiędzy modułami. Przykłady poleceń które podałeś idą od interfejsu użytkownika w tym wypadku API do modułów, więc ponawiam swoje pytanie, po co mają moduły wysyłać pomiędzy sobą komendy? Jakiś przykład?

2
neves napisał(a):

Żeby mieć modularny monolit nie jest potrzebny ani CQRS, ani mediator. Jest to jedna z opcji, ale jak zauważyłeś nie znamy wymagań, więc chyba najlogiczniej jest wtedy iść w minimalne rozwiązanie?

Tak, ale zauwaz ze ja zachecalem rowniez do zainteresowania sie DDD, nawet w lekkiej formie. W przeciwnym razie czym niby bedzie taki modularny monolit? Aplikacja rozbita na kilka projektow? Jesli tak, to czy naprawde mowa o modularnosci? Dla mnie to niby-modularnosc poprzez przeniesienie namespaceow (folderow) o poziom wyzej. Ale popraw mnie jesli sie myle, zawsze jestem chetny do nauki nowych rzeczy.

Moje pytanie brzmiało po co wysyłać komendy pomiędzy modułami. Przykłady poleceń które podałeś idą od interfejsu użytkownika w tym wypadku API do modułów, więc ponawiam swoje pytanie, po co mają moduły wysyłać pomiędzy sobą komendy? Jakiś przykład?

Jak najbardziej. Zalozmy ze mamy strone bookowania pokojow w hotelu. Dla uproszczenia przyjmijmy ze mamy 2 nastepujace konktesty: Rooms (do zarzadzania hotelami i pokojami, moze nie najlepsze imie w tym przypadku) oraz Bookings. UI wysyla polecenie ktore jest handlowane w konktescie Bookings- CreateBooking. Naturalnie, musimy zarezerwowac pokoje, ale to nie jest w gestii kontekstu Bookings. Od walidowania zasad biznesowych rezerwacji pokoju jest Rooms. W takim przypadku process odpowiadajacy za bookowanie w kontekscie Booking wysyla polecenie ReserveRooms, ktore jest handlowane w kontekscie Rooms. Jesli jakis pokoj nie moze byc zarezerowany, bo np. nie ma juz wolnych miejsc (ktos inny w miedzy czasie zdazyl zarezerwowac), proces w Rooms rzuca wyjatek (lub emituje event, zaleznie od przyjetego podejscia) i cala operacja bookowania zostaje przerwana.

0

Nie trafia do mnie ten przykład :), wystarczy dać jakiś process manager/sage która będzie orkiestrowała przebieg procesu i potrzeba komunikacji pomiędzy Bookings a Rooms kompletnie znika. I wtedy nie mamy loose coupling, tylko no coupling at all. W wersji synchronicznej taki proces można orkiestrować z poziomu kontrolera, albo serwisu aplikacyjnego jak się dorobiliśmy warstwy logiki aplikacji. Aczkolwiek wtedy można dysputować czy logika biznesowa nie wycieka nam poza model domenowy, ale z drugiej strony mamy w pełni autonomiczne moduły, a to znacznie ułatwia życie, skalowanie i testowanie.

0

@neves: Alez oczywiscie, process manager/saga to bardzo dobre rozwiazanie przy bardziej rozbudowanej domenie i integracji miedzy kontekstami. Specjalnie o tym nie wspominalem bo omawiamy jednak troche prostsze scenariusze. Fakt, wtedy nie wysylasz polecen dla jednego kontekstu bezposrednio z innego. Co nie zmienia faktu ze komunikacja miedzy konktestami sie odbywa, tylko ze posrednio. No i PM/Saga to dodatkowa warstwa, w prostszych komunikacjach miedzydomenowych mogaca przyniesc niepotrzebna komplikacje. Nie zapominaj jednak, ze i tak pewna komunikacja bedzie sie odbywac. Kontekst A bedzie konsumowal eventy z konktestu B, chociazby w celu denormalizacji danych. Moim zdaniem (nie tylko moim zreszta) w modularnych aplikacjach o jakich tutaj mowa raczej nie ma potrzeby na wprowadzanie PM/Sagas. Oczywiscie podkreslam- wszystko zalezy od wymagan i zlozonosci systemu.

Tutaj troche na ten temat

When should I use a process manager?
When your bounded context uses a large number of events and commands that would be difficult to manage as a collection point-to-point interactions between aggregates.

EDIT: Rowniez tutaj ciekawy artykul

EDIT 2: Wprowadzajac PM/Saga musimy juz wprowadzic eventual consistency. Nie wazne ze komunikacja w modularnym monolicie nadal odbywa sie w ramach jednego procesu- PM bedzie oczekiwal na eventy aby wyslac kolejne polecenia. W takiej sytacji musimy juz rozwazyc zmiany architektury UI, zapewne bedzie potrzebna komunikacja dwu-kierunkowa miedzy serwerem a klientem, poniewaz skonczony proces (np. bookowania) nie "powroci" do kontrolera. Potrzebna bedzie dodatkowa infrastruktura odpowiedzialna za wyslanie komunikatu do klienta o zakonczonej (z powodzeniem lub nie) operacji, np. przy uzyciu SignalR. To z kolei bedzie wymagac korelacji poczatkowego requestu od klienta ze wszystkimi operacjami wykonywanymi w trakcie, az do zwrocenia wyniku do klienta na podstawie tejze wlasnie korelacji.

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