Clean Architecture w połączeniu z Minimal API to wyjątkowo skuteczne podejście do budowy skalowalnych, testowalnych i łatwych w utrzymaniu aplikacji .NET. Koncepcja Roberta C. Martina kładzie nacisk na separację odpowiedzialności, inwersję zależności oraz ścisłe przestrzeganie kontraktów pomiędzy warstwami. Implementacja Clean Architecture z Minimal API wymaga przemyślanego rozmieszczenia warstw, precyzyjnych interfejsów oraz jasnych abstrakcji, które oddzielają logikę domenową od ram technicznych.
Fundamenty clean architecture w kontekście minimal API
Architektura Clean Architecture obejmuje cztery główne warstwy:
- domain,
- application,
- infrastructure,
- presentation.
Wszystkie one działają na zasadzie inwersji zależności: zewnętrzne warstwy zależą od wewnętrznych, nie odwrotnie. Abstrakcje definiuje się w centrum systemu, a ich implementacje powstają w infrastrukturze. Dzięki temu Application Core pozostaje niezależny od technologii i frameworków, co drastycznie ułatwia testowanie oraz utrzymanie kodu – podczas kompilacji warstwa prezentacji widzi wyłącznie interfejsy warstw wewnętrznych, a powiązania techniczne realizowane są przez dependency injection podczas działania aplikacji.
Warstwa domeny – serce architektury i kontrakty
Centralnym miejscem w Clean Architecture pozostaje warstwa domeny. Odpowiada za logikę biznesową, reguły i podstawowe abstrakcje używane przez cały system.
Najważniejsze składniki tej warstwy to:
- encje,
- obiekty wartości,
- agregaty,
- wydarzenia domenowe.
Kontrakty domeny, takie jak interfejsy repozytoriów, zapewniają niezależność od implementacji technologicznych, a specyfikacje i usługi domenowe pozwalają porządkować nawet bardzo złożoną logikę biznesową. Wyjątki niestandardowe oraz klauzule ochronne skutecznie pilnują reguł biznesowych i zabezpieczają integralność systemu.
Warstwa aplikacji – orkiestracja oraz przypadki użycia
Warstwa aplikacji stanowi pośrednika między domeną a światem zewnętrznym – organizuje kluczowe przypadki użycia, waliduje, koordynuje oraz zarządza przepływem informacji. Jest niezależna od infrastruktury oraz prezentacji i bazuje wyłącznie na abstrakcjach.
W tej warstwie najczęściej można znaleźć:
- serwisy aplikacyjne – wdrażają logikę use case;
- przypadki użycia – orkiestrują operacje optymalnie wykorzystując encje domenowe;
- wielowarstwową walidację – najczęściej z wykorzystaniem FluentValidation i pipeline behaviors (MediatR);
- CQRS – czytelny podział na komendy i zapytania;
- MediatR – redukcja zależności przez pośredników.
DTOs są tu nieodłącznym elementem pozwalającym efektywnie wymieniać dane między warstwami bez naruszania hermetyzacji i porządku architektury.
Warstwa infrastruktury – dostarczenie zależności zewnętrznych
Za integrację z bazami danych, usługami zewnętrznymi czy logowaniem odpowiedzialna jest warstwa infrastruktury. Obsługuje ona najważniejsze zadania techniczne, takie jak:
- implementacja repozytoriów i wzorca unit of work,
- rejestracja oraz konfiguracja usług przez AddScoped i ServiceExtensions,
- mapowanie oraz izolacja modeli technicznych od kodu domenowego,
- wzorce odporności (retry, circuit breaker) dzięki Polly.
Klasy infrastruktury powinny być domyślnie internal i wyraźnie oddzielone od warstw biznesowych — minimalizuje to ryzyko niepożądanych zależności w kodzie.
Warstwa prezentacji – Minimal API jako punkt wejścia
Warstwa prezentacji odpowiada za komunikację aplikacji ze światem zewnętrznym. Zastosowanie Minimal API wymaga przemyślanej struktury oraz rozdziału odpowiedzialności. Najczęściej spotykane rozwiązania obejmują:
- klastryzację endpointów w statycznych klasach z metodami rozszerzającymi,
- osobne klasy endpointów dla testowalności,
- użycie bibliotek organizujących logikę Minimal API, np. Carter czy FastEndpoints.
Prezentacja mapuje żądania HTTP na komendy lub zapytania warstwy aplikacji i nie powinna zawierać logiki biznesowej. Walidacja wstępna dotyczy jedynie sprawdzania formatu danych wejściowych.
Kontrakty między warstwami – interfejsy, DTOs i wydarzenia
Interfejsy oraz DTOs jawnie oddzielają odpowiedzialności i zapewniają luźne powiązania między warstwami. Praktyką jest grupowanie ich we wspólnym folderze (Abstractions) oraz implementowanie w warstwach zewnętrznych.
- Interfejsy – definiują kontrakty pomiędzy warstwami;
- DTOs – efektywnie przenoszą dane, redukując zależności;
- specyfikacje – pozwalają budować wielopoziomowe wyszukiwania;
- wydarzenia domenowe i integracyjne – wspierają asynchroniczną komunikację i integracje systemów.
Organizacja projektu i struktura folderów
Dobrze zorganizowana struktura Clean Architecture opiera się przynajmniej na trzech, a często czterech projektach:
- Presentation – punkty wejścia, endpointy Minimal API,
- Application – logika aplikacyjna, interfejsy, przypadki użycia,
- Infrastructure – integracje, repozytoria, migracje,
- Domain (opcjonalnie) – czyste encje, reguły i abstrakcje domeny.
Przykładowe uporządkowanie folderów i projektów rozwiązania:
- MyProjectSolution
- MyProject.Application – Services, UseCases, Interfaces;
- MyProject.Domain – Entities, ValueObjects, Interfaces;
- MyProject.Infrastructure – Data, ExternalServices.
Taki porządek gwarantuje spójność architektoniczną i przejrzystą nawigację także w dużych projektach.
Wstrzykiwanie zależności i konfiguracja
Fundamentem Clean Architecture w Minimal API jest wstrzykiwanie zależności (dependency injection) wykorzystujące kontener DI wbudowany w ASP.NET Core.
Przykład rejestracji usług i repozytoriów:
- builder.Services.AddScoped<IUserRepository, InMemoryUserRepository>();
- builder.Services.AddScoped<UserService>();
- builder.Services.AddControllers();
Program.cs wiąże wszystkie warstwy, inicjuje pipeline middleware, API i narzędzia dokumentacyjne typu Swagger. Minimal API pozwala wstrzykiwać usługi bezpośrednio do endpointów, upraszczając kod i testowanie. MediatR integruje się przez pipeline DI, automatyzując obsługę komend i handlerów.
Testowanie i jakość kodu
Architektura warstwowa znacząco ułatwia testowanie – Application Core nie zależy od technologii, co przekłada się na szybkie i niezawodne testy jednostkowe (np. xUnit, Moq). Testy integracyjne obejmują pełne żądania przez warstwy prezentacji do bazy danych.
W obszarze testów warto objąć:
- logikę domenową i aplikacyjną,
- walidację zarówno na wejściu, jak i na poziomie use case,
- pełne przepływy HTTP przez TestServer – funkcjonalność Minimal API,
- reguły architektoniczne (np. z wykorzystaniem NetArchTest).
Dodatkowo, structured logging, monitoring oraz sterowanie obsługą błędów jako cross-cutting concerns znacząco poprawiają bezpieczeństwo i jakość kodu produkcyjnego.
Zaawansowane wzorce i praktyki
Dla bardziej złożonych projektów Clean Architecture daje możliwość wdrożenia zaawansowanych wzorców, takich jak:
- vertical slice architecture – podział kodu według funkcjonalności biznesowej,
- event-driven architecture – integracja wydarzeń domenowych przez MediatR,
- CQRS – pełny rozdział odczytów i zapisów dla optymalnej skalowalności,
- własny pipeline (middleware, pipeline behaviors) dla cross-cutting concerns,
- zaawansowana kontrola uprawnień (role, permission-based, integracja z Keycloak).
Podsumowanie kluczowych zaleceń
Clean Architecture wraz z Minimal API umożliwia tworzenie nowoczesnych, skalowalnych systemów, które są łatwe w testowaniu i utrzymaniu. Najważniejsze zasady to przejrzysty podział warstw, kontraktowe podejście do komunikacji oraz ścisła separacja zależności technologicznych od logiki biznesowej.
Struktura warstwowa upraszcza testowanie i rozwijanie aplikacji, a dodatkowe narzędzia jak DTOs, MediatR czy NetArchTest wzmacniają spójność i niezawodność projektu. W praktyce zawsze należy dostosować stopień rozwarstwienia do rozmiaru projektu – pragmatyzm jest kluczowy. Równowaga między architekturą a złożonością wdrożenia oraz konsekwencja w stosowaniu dobrych praktyk daje najlepsze efekty w codziennej pracy z .NET.