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.