Clean Architecture to jedno z kluczowych podejść architektonicznych stosowanych w nowoczesnym tworzeniu oprogramowania .NET, zapewniające skalowalność, testowalność i łatwą utrzymywalność aplikacji. Koncepcja stworzona przez Roberta C. Martina (Uncle Bob) stała się szczególnie popularna w .NET ze względu na wyraźny podział logiki biznesowej od infrastruktury. Zgodnie z ideą Clean Architecture, aplikacje .NET dzielone są na koncentryczne warstwy, a zależności zawsze kierowane są od zewnętrznych do wewnętrznych, chroniąc biznes przed wpływem frameworków, baz danych czy interfejsów użytkownika. Do najważniejszych zalet należą: zwiększona testowalność, prostsze utrzymanie i elastyczność przy zmieniających się wymaganiach biznesowych. W praktyce Clean Architecture w .NET często angażuje wzorce takie jak CQRS z MediatR, co jeszcze bardziej usprawnia rozdział odczytu i zapisu. Znane szablony projektów – np. Jasona Taylora czy Steve’a Smitha – dostarczają kompletne przykłady do wdrożenia w nowych projektach ASP.NET Core, łącząc rynkowe standardy z praktycznymi narzędziami.
Fundamenty teoretyczne Clean Architecture
Nurt Clean Architecture wywodzi się z zasad SOLID oraz Domain-Driven Design. Jest odpowiedzią na słabości klasycznych architektur warstwowych, przesuwając logikę biznesową i modele aplikacji do centrum systemu i eliminując ich bezpośrednią zależność np. od dostępu do danych. Clean Architecture łączy najlepsze elementy Hexagonal Architecture (Ports and Adapters) oraz Onion Architecture, prezentując praktyczne sposoby implementacji dla profesjonalnych zespołów .NET.
Filozofia Clean Architecture bazuje na inwersji zależności: moduły wysokopoziomowe nie zależą od niskopoziomowych, ale oba korzystają z abstrakcji. W aplikacjach .NET warstwa domenowa wystawia interfejsy, a ich konkretne wdrożenia umieszczane są w infrastrukturze. Dzięki takiemu podziałowi możliwe jest łatwe testowanie jednostkowe domeny bez zależności od reszty aplikacji oraz niekłopotliwa wymiana np. dostępu do danych bez wpływu na reguły biznesowe.
Kluczowym elementem Clean Architecture są koncentryczne warstwy – struktura „cebulowa”, w której każda warstwa zależy wyłącznie od wewnętrznych. Zmiany w warstwach zewnętrznych nie mają wpływu na warstwy wewnętrzne, co znacznie poprawia trwałość i elastyczność systemu. Sercem całości jest warstwa domenowa – wolna od zewnętrznych zależności, skupiona na czystych regułach biznesowych.
Anatomia warstw Clean Architecture w .NET
Warstwa domenowa jako rdzeń architektury
Warstwa domenowa to podstawa Clean Architecture w środowisku .NET. Znajdują się tu fundamentalne reguły biznesowe, encje, value objects, agregaty, zdarzenia domenowe oraz specyfikacje. Encje nie są jedynie kontenerami danych – zawierają także metody i reguły biznesowe, gwarantując spójność procesów biznesowych.
Projektując warstwę domenową w .NET należy zadbać o zachowanie pełnej niezależności od zewnętrznych bibliotek. Przykład: encja Product implementuje metodę ValidateBusinessRules pozwalającą sprawdzić, czy cena jest dodatnia, a stan magazynowy nie jest ujemny. Taka enkapsulacja sprawia, że logika nie rozprasza się po innych warstwach systemu.
W tej warstwie często definiuje się interfejsy repozytoriów, których realizacja następuje dopiero w infrastrukturze. To pozwala warstwie domenowej komunikować się ze światem zewnętrznym bez bezpośrednich zależności od technologii. Znajdziemy tu także serwisy domenowe, dedykowane operacjom nienależącym do żadnej konkretnej encji, ale będącym częścią logiki biznesowej.
Warstwa aplikacyjna jako orkiestrator przypadków użycia
Warstwa aplikacyjna pełni funkcję orkiestratora, definiując interfejsy operacji i koordynując działania pomiędzy domeną a infrastrukturą. W .NET popularne są tu wzorzec CQRS oraz biblioteka MediatR, dzięki którym czytelnie rozdzielamy zapis od odczytu oraz zapewniamy luźne powiązania pomiędzy komponentami.
- Komendy – reprezentują operacje zmieniające stan aplikacji;
- Zapytania – służą tylko do odczytu danych, nie wywołują skutków ubocznych;
- Handlery – realizują konkretne przypadki użycia na bazie komend lub zapytań;
- Interfejsy warstwy aplikacyjnej – umożliwiają wstrzykiwanie zależności oraz ułatwiają testowanie jednostkowe.
Taki podział zapewnia spójność przepływu danych oraz doskonałą podstawę pod automatyczne testy.
Warstwa aplikacyjna realizuje też często zagadnienia przekrojowe (walidacja, autoryzacja, logowanie, cache’owanie) przy pomocy wzorca Chain of Responsibility i dedykowanych interceptorów (znajdziemy je np. w projekcie SharedKernel).
Warstwa infrastrukturalna jako implementacja szczegółów technicznych
Warstwa infrastrukturalna odpowiada za wszelkie wdrożenia techniczne: dostęp do baz danych, obsługę plików, integracje API, płatności, pocztę, storage i inne. W .NET najczęściej stosuje się tu wzorzec Repository, bazując na Entity Framework Core do obsługi baz SQL Server czy innych baz relacyjnych.
Konieczna jest poprawna konfiguracja DbContext, mapowań encji oraz połączenia z bazą. Ważnym aspektem są migracje, które pozwalają ewoluować schemat bazy razem z aplikacją. Implementacje Unit of Work oraz transakcyjność baz danych zapewniają stabilność operacji biznesowych.
Komunikacja z zewnętrznymi API oraz innymi usługami również osadzana jest w tej warstwie. Wszystkie zależności techniczne powinny być opakowane w abstrakcje z domeny lub aplikacji, co zapewnia swobodę zmiany implementacji. Ważną rolę odgrywa tu wbudowany w ASP.NET Core dependency injection, umożliwiający łatwą podmianę konkretnych wdrożeń w runtime.
Warstwa prezentacji jako interfejs użytkownika
To tutaj użytkownik komunikuje się z systemem poprzez kontrolery Web API, interfejsy UI oraz inne komponenty prezentacyjne. W ASP.NET Core warstwa ta może przyjmować różne formy – od MVC przez Web API do SPA z Angular lub React.
Praktyka podpowiada, by kontrolery były możliwie najcieńsze i delegowały obsługę logiki do warstwy aplikacyjnej. Ich zadaniem są: obsługa żądań HTTP, walidacja wejścia, zwracanie odpowiednich odpowiedzi. Biznes powinien pozostawać w pełni w aplikacji, co ułatwia testowanie i dalszy rozwój systemu.
Nowoczesne rozwiązania (Minimal APIs) umożliwiają zwięzłe definiowanie endpointów. W przypadku integracji z front-endami jak Angular czy React wymagane jest poprawne skonfigurowanie CORS oraz rozdział uwierzytelniania i autoryzacji. Najnowsze szablony Clean Architecture często już zawierają gotowe ustawienia dla takich integracji.
Implementacja Clean Architecture w ASP.NET Core
Konfiguracja projektu i struktura rozwiązania
Implementacja Clean Architecture w ASP.NET Core rozpoczyna się od świadomej organizacji struktury projektu. Warstwy oddzielane są jako projekty bibliotek klas:
- Domain/Core,
- Application,
- Infrastructure,
- Presentation (Web API lub MVC).
Każdy projekt ma swoje jasno określone odpowiedzialności oraz zależności kierowane zawsze do centrum (warstwa domenowa). Foldery takie jak Core (zawierający Domain i Application), Infrastructure czy Presentation pomagają zachować granice architektoniczne i lepszą czytelność rozwiązania.
Proces tworzenia aplikacji Clean Architecture w Visual Studio obejmuje te etapy:
- Tworzenie solution i dodanie projektu Domain,
- Dodanie projektu Application zależnego od Domain,
- Stworzenie projektu Infrastructure z referencjami do Application i Domain,
- Utworzenie projektu Presentation, w razie potrzeby referencjonującego pozostałe warstwy dla konfiguracji dependency injection.
Taki podział zapewnia maksymalną izolację logiki biznesowej od szczegółów technicznych oraz gwarantuje prostotę testowania i utrzymania kodu.