Dodajesz w API obsługę zamówień i nagle kontroler zna EF Core, reguły rabatów oraz format odpowiedzi dla klienta. Clean Architecture zwykle pomaga odsunąć takie szczegóły od logiki biznesowej, ale przy prostym CRUD może dołożyć więcej warstw niż problemów rozwiąże.
Architektura ma upraszczać zmiany w logice biznesowej
- Punktem odniesienia są reguły biznesowe, a nie liczba projektów w rozwiązaniu.
- Kierunek zależności powinien prowadzić od szczegółów technicznych do centrum aplikacji.
- Abstrakcje przydają się tam, gdzie wyznaczają granicę odpowiedzialności.
- Prostszy układ wystarczy, gdy aplikacja głównie zapisuje i odczytuje dane.

Reguły biznesowe powinny przetrwać zmianę bazy i interfejsu
Wyobraź sobie, że sklep zmienia sposób naliczania rabatu. Jeśli zasada „zniżka nie może przekroczyć wartości koszyka” siedzi w kontrolerze ASP.NET Core albo w zapytaniu do bazy, zmiana dotyka kodu, który powinien zajmować się innymi sprawami. W centrum aplikacji umieść politykę biznesową, a bazę, interfejs i integracje potraktuj jako wymienne szczegóły.
Robert C. Martin opisał tę zasadę jako Dependency Rule: zależności w kodzie mają wskazywać do środka architektury. Przepływ wykonania może iść od kontrolera do przypadku użycia, a później do zapisu, lecz kod przypadku użycia nie powinien odwoływać się wprost do klasy kontrolera ani konkretnego dostawcy bazy.
W aplikacji .NET oznacza to zwykle, że projekt z regułami i przypadkami użycia nie zależy od projektu infrastruktury. Interfejs potrzebny do zapisu może należeć do warstwy aplikacji, a jego implementacja korzystająca z EF Core trafia do infrastruktury. Sam podział na foldery lub projekty niczego jednak nie gwarantuje, jeśli referencje pozwalają warstwom omijać tę granicę.
Prosty CRUD nie potrzebuje osobnego projektu dla każdej warstwy
Przy ekranie administracyjnym, który zapisuje kilka pól i nie ma istotnych reguł, rozbudowany szkielet potrafi spowolnić każdą zmianę. Microsoft Learn w opisie architektury aplikacji .NET wskazuje, że prosta aplikacja utrzymaniowa oparta na CRUD może nie potrzebować wzorców DDD. To dobry argument za doborem architektury do złożoności domeny, a nie za kopiowaniem szablonu do każdego repozytorium.
| Sytuacja | Prawdopodobny skutek | Co zrobić |
|---|---|---|
| Endpoint zapisuje proste dane bez reguł poza walidacją pól | Dodatkowe interfejsy i mapowania mogą utrudnić odczyt przepływu | Zacznij od prostego układu i wydzielaj granice, gdy pojawi się uzasadnienie |
| Zmiana jednej reguły wpływa na kilka ekranów lub integracji | Logika w kontrolerach i usługach technicznych zaczyna się dublować | Umieść regułę w modelu domenowym lub przypadku użycia |
| Wymiana dostawcy zewnętrznego wymaga zmian w logice biznesowej | Centrum aplikacji zna szczegóły infrastruktury | Wprowadź interfejs na granicy i implementację po stronie infrastruktury |
Moja praktyczna granica jest prosta: jeśli kod głównie przenosi dane, wybieram prostszy układ; jeśli musi pilnować zasad, które zmieniają wynik biznesowy, oddzielam przypadki użycia i domenę od infrastruktury. Nie trzeba od razu dzielić rozwiązania na wiele projektów. Można najpierw uporządkować kod w jednym projekcie, a granice wzmocnić, gdy referencje i odpowiedzialności zaczną się mieszać.
Przypadek użycia zamówienia oddziela decyzję biznesową od zapisu
Załóżmy, że zamówienie może dostać rabat zależny od rodzaju klienta, ale końcowa kwota nie może spaść poniżej ustalonego minimum. Kontroler odbiera żądanie i przekazuje dane dalej; przypadek użycia koordynuje obsługę zamówienia, a regułę obliczenia i ograniczenie kwoty trzyma model domenowy.
Przypadek użycia może pobrać dane klienta przez abstrakcję, uruchomić regułę domenową i przekazać wynik do zapisu. Implementacja korzystająca z `DbContext` pozostaje na zewnątrz. Dzięki temu test reguły rabatowej nie musi uruchamiać ASP.NET Core ani łączyć się z bazą, a test integracyjny nadal może sprawdzić, czy implementacja poprawnie zapisuje zamówienie.
Tu łatwo dodać warstwy, które niczego nie wnoszą: interfejs z jedną implementacją, pośrednik wywołujący tylko kolejną usługę albo mapowanie kilku identycznych modeli bez odrębnej odpowiedzialności. Każda granica powinna chronić decyzję albo izolować szczegół techniczny. Jeśli po dodaniu klasy trudniej prześledzić przepływ „żądanie → reguła → zapis”, ten podział wymaga ponownego przemyślenia.
DbContext nie zawsze potrzebuje kolejnego repozytorium
W .NET `DbContext` z EF Core już udostępnia operacje pracy z danymi. Owijanie każdego zestawu wierszy w generyczne `IRepository
Nie ma też obowiązku chowania każdej zależności za interfejsem. Jeśli przypadek użycia korzysta bezpośrednio z konkretnego narzędzia, świadomie akceptujesz sprzężenie z nim. Gdy zależność zmienia się często, utrudnia testowanie reguły albo wypycha szczegóły infrastruktury do centrum, wtedy abstrakcja zaczyna zarabiać na swój koszt.
W dyskusjach programistów powracają dwa zastrzeżenia: nadmiar skakania między plikami i boilerplate w małych aplikacjach. Z drugiej strony, przy skomplikowanych regułach wyraźne granice pomagają utrzymać kod w ryzach. Oba odczucia są uzasadnione; decyduje to, czy warstwy zmniejszają koszt zmian w konkretnym systemie.
Najlepsza architektura zostawia miejsce na zmianę bez ceremonii
Clean Architecture w .NET sprowadza się do pilnowania kierunku zależności i umieszczania reguł tam, gdzie mogą działać niezależnie od interfejsu oraz bazy. Nie zaczynaj od liczby projektów ani obowiązkowego zestawu wzorców; zacznij od pytania, która zmiana w aplikacji jest dziś trudna i jaka granica odseparuje jej przyczynę.
Jeśli ta granica jest jasna, podział na domenę, aplikację i infrastrukturę może pomóc. Gdy projekt tylko zapisuje formularz do tabeli, prostszy kod może być równie czytelny i łatwiejszy w utrzymaniu. Wróć do zamówienia z początku: architektura spełnia swoje zadanie, gdy zmiana rabatu nie wymaga grzebania w kontrolerze ani szczegółach bazy.
