DDD nie zaczyna się od folderu `domain`
Eric Evans opisuje DDD jako zestaw wzorców i definicji służących pracy z modelem domeny, a Martin Fowler podkreśla, że sednem jest modelowanie złożonej domeny i rozwijanie języka używanego wspólnie przez programistów oraz ekspertów domenowych. [1][2][4]
Sama struktura `domain/application/infrastructure` nie oznacza więc DDD. Można mieć perfekcyjnie nazwane katalogi i nadal operować wyłącznie na DTO oraz procedurach bez modelu domenowego. Można też stosować strategiczne idee DDD bez klasycznego układu warstw.
Strategic DDD i Tactical DDD to dwa różne poziomy decyzji
Strategiczne DDD zajmuje się podziałem większej domeny na subdomeny i Bounded Contexts oraz relacjami między nimi. Microsoft opisuje analizę domeny jako etap identyfikacji subdomen i ograniczonych kontekstów, a Fowler wskazuje Bounded Context jako centralny wzorzec strategicznego DDD. [3][7]
Taktyczne DDD działa wewnątrz konkretnego kontekstu i obejmuje między innymi encje, Value Objects, agregaty i Domain Services. Microsoft wyraźnie rozdziela analizę strategiczną od taktycznego modelowania. [8]
W frontendzie często warto zacząć od poziomu strategicznego i dopiero później zdecydować, czy dany kontekst rzeczywiście potrzebuje bogatego modelu taktycznego.
Bounded Context nie jest warstwą techniczną
Fowler definiuje Bounded Context jako granicę, wewnątrz której model jest spójny i ma jednoznaczne znaczenie. Ten sam termin, na przykład `Customer` albo `Product`, może mieć inne znaczenie w różnych kontekstach. [3]
Z tego wynika ważna konsekwencja dla frontendu: `frontend`, `backend`, `React app`, `route` czy `micro-frontend` nie są automatycznie Bounded Contexts. Granica kontekstu wynika z modelu i języka domenowego, a nie z technologii. Praktycy DDD w frontendzie również zwracają uwagę, że frontend jako całość nie powinien być mechanicznie traktowany jako osobny kontekst. [15][16]
Kiedy DDD w frontendzie ma sens
| Sygnał | Kierunek | Dlaczego |
|---|---|---|
| Wiele zmiennych reguł biznesowych | DDD | Model domenowy ogranicza rozproszenie reguł |
| Te same pojęcia mają różne znaczenia w produkcie | DDD | Bounded Contexts pozwalają utrzymać kilka spójnych modeli |
| Wiele zespołów i ekspertów domenowych | DDD | Ubiquitous Language zmniejsza niejednoznaczność |
| Prosty CRUD i formularze | Prostsza modularność | Koszt taktycznego DDD może przewyższać korzyść |
| Frontend głównie odwzorowuje API | Prostsza modularność | Niewiele własnej logiki domenowej do modelowania |
| Mała aplikacja z jedną spójną domeną | Prostsza modularność | Granice i warstwy mogą nie wnosić realnej wartości |
Najmocniejszy sygnał to złożone, często zmieniające się reguły biznesowe widoczne także po stronie klienta: konfiguratory, procesy wieloetapowe, pricing, uprawnienia, zależności między stanami, rozbudowane workflow lub różne modele tego samego pojęcia w różnych częściach produktu.
DDD jest również uzasadnione, gdy kilka zespołów rozwija duży produkt i te same słowa mają różne znaczenia w różnych obszarach. Bounded Contexts są właśnie mechanizmem radzenia sobie z wieloma modelami i zespołami. [3]
DDD Crew zaleca najpierw zrozumieć domenę, wskazać strategicznie najważniejsze subdomeny i dopiero później definiować odpowiedzialności Bounded Contexts oraz kodować model. [10]
Kiedy DDD jest przerostem formy
Jeżeli frontend głównie pobiera dane, wyświetla je, edytuje formularze i wysyła proste komendy CRUD bez istotnych reguł biznesowych po stronie klienta, pełny zestaw taktycznych wzorców DDD zwykle nie rozwiązuje realnego problemu.
Microsoft wprost wskazuje, że dla prostego kontekstu CRUD anemiczny model danych może być wystarczający i nie zawsze warto wdrażać bardziej złożone wzorce DDD. Dopiero duża liczba zmiennych reguł biznesowych uzasadnia bogatszy model. [9]
To samo kryterium warto stosować w frontendzie: poziom architektury powinien odpowiadać złożoności domeny, a nie ambicji technicznej zespołu.
Ubiquitous Language powinien być widoczny także w kodzie UI
Ubiquitous Language to wspólny, rygorystyczny język budowany przez programistów i ekspertów domenowych na bazie modelu. Fowler podkreśla, że język powinien ewoluować wraz ze zrozumieniem domeny. [4]
W frontendzie oznacza to, że nazwy use case'ów, akcji, typów, modułów, ekranów i stanów powinny używać terminów biznesowych zamiast technicznych skrótów tam, gdzie opisują zachowanie domeny.
Jeżeli biznes mówi `approveApplication`, a frontend wszędzie operuje na `setFlag2` albo `handleData`, zespół traci jedną z głównych korzyści DDD: kod przestaje być czytelnym nośnikiem modelu.
Organizuj duży frontend według domen, a nie wyłącznie typów plików
Fowler wskazuje, że podział najwyższego poziomu na `view/model/data` może wystarczać w mniejszych systemach, ale wraz ze wzrostem aplikacji lepiej dzielić najwyższy poziom na moduły domenowe, które dopiero wewnętrznie mają własne warstwy. [5]
Aktualna dokumentacja Nx pokazuje podobny kierunek: foldery mogą wyznaczać granice ownershipu domen, a wewnątrz domen biblioteki mogą być klasyfikowane jako `feature`, `ui`, `data-access` i `util`. [12]
Praktyczny układ może więc wyglądać jak `libs/orders/...`, `libs/billing/...`, `libs/identity/...`, zamiast jednego globalnego `components/`, `services/` i `models/`.
Oddziel prezentację od logiki domenowej
Fowler opisuje rozdzielenie prezentacji, logiki domenowej i dostępu do danych jako skuteczny sposób modularizacji aplikacji informacyjnych. Wskazuje też, że UI jest trudniejsze do testowania, dlatego logika domenowa korzysta na umieszczeniu poza warstwą prezentacji. [5][6]
W praktyce frontendowej komponent powinien przede wszystkim renderować, obsługiwać zdarzenia UI i delegować operacje. Reguły takie jak `czy zamówienie można anulować`, `jak policzyć rabat` albo `jaki status jest dozwolony po poprzednim` powinny mieć jedno jawne miejsce poza JSX, template lub komponentem.
Nie oznacza to zakazu logiki prezentacyjnej w komponencie. Walidacja widoku, widoczność elementu, lokalny focus czy animacja to inny rodzaj logiki niż reguła biznesowa.
Praktyczne warstwy w jednym kontekście frontendu
| Warstwa | Odpowiedzialność | Przykładowe elementy |
|---|---|---|
| presentation / ui | Renderowanie i zachowanie interfejsu | components, routes, view state |
| application | Koordynacja przypadków użycia | commands, use cases, orchestration |
| domain | Reguły, pojęcia i niezmienniki domeny | entities, value objects, policies |
| infrastructure / data-access | Integracje techniczne | HTTP, storage, SDK, mappers |
DDD nie narzuca jednego obowiązkowego układu katalogów. Rozdzielenie `presentation`, `application`, `domain` i `infrastructure` jest praktyczną interpretacją opartą na separacji odpowiedzialności, a nie formalnym wymaganiem Evansa. [1][5]
Warstwa domenowa może zawierać reguły, typy i zachowanie niezależne od frameworka; application może koordynować use case'y; infrastructure może adaptować HTTP, storage i zewnętrzne SDK; presentation może zawierać komponenty, routing i stan czysto interfejsowy.
Model API i model domeny frontendu nie muszą być tym samym
Bounded Context może mieć własny model tego samego pojęcia, a różne konteksty mogą mapować między swoimi reprezentacjami. Fowler podaje `Customer` i `Product` jako typowe przykłady pojęć o różnych znaczeniach w różnych kontekstach. [3]
Dlatego DTO z backendu nie musi przenikać bezpośrednio do każdego komponentu. Mapper lub adapter może przekształcić kontrakt transportowy do modelu używanego przez konkretny kontekst frontendu.
Taki krok ma największą wartość, gdy API jest współdzielone przez kilka klientów, jest historyczne, łączy kilka domen albo zmienia się niezależnie. Dla prostego endpointu CRUD dodatkowa warstwa mapowania może być zbędna.
Encje, Value Objects i agregaty: używaj selektywnie
DDD rozróżnia między innymi Entities, Value Objects, Services i Aggregates. Fowler wskazuje te pojęcia jako część klasyfikacji wprowadzonej przez Evansa, a Microsoft opisuje agregaty jako taktyczny wzorzec utrzymujący spójność modelu. [2][8]
Frontend nie powinien jednak kopiować modelu backendowego 1:1 tylko po to, aby mieć te same klasy. Value Object może być bardzo użyteczny dla `Money`, `DateRange` albo `Email`, jeżeli ma własne niezmienniki. Agregat ma sens wtedy, gdy klient rzeczywiście musi pilnować reguł spójności konkretnego modelu.
Jeżeli typ jest jedynie strukturą danych do renderowania tabeli, prosty typ TypeScript może być lepszy niż encja z fabryką, repozytorium i warstwą usług.
Stan UI to nie to samo co stan domenowy
React rozróżnia problemy organizacji stanu i zaleca unikanie redundantnego oraz zduplikowanego stanu. To dobra baza techniczna, ale samo użycie reducerów, store'a lub sygnałów nie tworzy modelu domenowego. [14]
Stan typu `isModalOpen`, aktywna zakładka czy pozycja scrolla należy do prezentacji. Stan reprezentujący cykl życia zamówienia, dostępne przejścia procesu albo reguły konfiguratora może należeć do modelu domenowego.
Rozdzielenie tych dwóch kategorii ogranicza globalne store'y zawierające przypadkową mieszankę danych serwera, zachowań biznesowych i szczegółów UI.
Granice muszą być egzekwowane, nie tylko narysowane
Nx pozwala definiować tagi projektów i deklaratywne reguły zależności, na przykład blokować importy pomiędzy określonymi scope'ami lub typami bibliotek. Reguła `@nx/enforce-module-boundaries` może sprawdzać importy podczas lintowania. [11]
Nx obsługuje też wiele wymiarów tagów, dzięki czemu można osobno modelować `scope`, typ biblioteki, stabilność czy stronę klient/server. [13]
Dla DDD oznacza to możliwość zapisania decyzji architektonicznych jako reguł CI, na przykład: `billing` nie importuje wnętrza `identity`, `ui` nie zależy od `data-access`, a `domain` nie importuje frameworka.
Frontend i backend powinny dzielić domenę, ale niekoniecznie identyczny model
Bounded Context jest granicą modelu i języka, dlatego nie powinien być wyznaczany automatycznie linią sieciową pomiędzy przeglądarką i serwerem. Praktyczne opracowania frontendowego DDD podkreślają, że konteksty mogą przecinać warstwy techniczne i być realizowane wspólnie przez frontend oraz backend. [15][16]
Jednocześnie konkretna reprezentacja modelu w kliencie może różnić się od reprezentacji serwerowej, bo ma inne potrzeby dotyczące interakcji, lokalnego stanu i prezentacji. Kluczowe jest zachowanie semantyki i jawne mapowanie, a nie kopiowanie klas.
Micro-frontend również nie powinien być tworzony tylko dlatego, że istnieje Bounded Context. Granica domenowa i granica deployowalnego artefaktu to osobne decyzje.
Testy są jednym z najmocniejszych argumentów za czystym modelem domenowym
Fowler wskazuje testowalność jako jedną z korzyści oddzielenia prezentacji od domeny. Logika poza UI może być testowana bez renderowania komponentów i bez zależności od szczegółów interfejsu. [5][6]
Dla frontendu oznacza to szybkie testy reguł, Value Objects, use case'ów i przejść stanu jako zwykłego TypeScript lub JavaScript. Testy komponentów pozostają potrzebne, ale nie muszą być jedynym miejscem weryfikacji reguł biznesowych.
Dobrym sygnałem ostrzegawczym jest sytuacja, w której każda zmiana reguły biznesowej wymaga montowania całego komponentu, mockowania routera, store'a, HTTP i przeglądarki.
Jak wdrażać DDD w istniejącym frontendzie bez wielkiego rewrite
DDD Crew proponuje proces iteracyjny: zrozumieć domenę, wskazać ważne subdomeny, zdefiniować odpowiedzialności kontekstów i dopiero potem kodować model. [10]
W istniejącym frontendzie bezpieczniej wybrać jeden problematyczny obszar, nazwać jego język, ustalić granicę, odseparować prezentację od reguł, zdefiniować publiczne API modułu i dopiero później przenosić kolejne funkcje.
Nie trzeba przenosić całej aplikacji. DDD można stosować tam, gdzie złożoność biznesowa daje zwrot z dodatkowego modelowania, a pozostałe obszary pozostawić jako prostsze feature modules.
- Zacznij od problemu biznesowego, nie od struktury katalogów.
- Zidentyfikuj terminy, reguły i niejednoznaczne pojęcia.
- Wyznacz jeden Bounded Context i jego publiczny kontrakt.
- Oddziel logikę domenową od komponentów i transportowych DTO.
- Dodaj tylko te wzorce taktyczne, które rozwiązują konkretną złożoność.
- Zapisz granice jako reguły lintowania lub CI.
- Mierz liczbę zależności, cykli, regresji i trudność zmian przekrojowych.
- Nie migruj prostych CRUD-ów tylko dla architektonicznej symetrii.

