# Clean Code Architecture C#: Kompletny Przewodnik i Pytania Rekrutacyjne 2026 > Opanuj Clean Code i Clean Architecture w C# z zasadami SOLID, wstrzykiwaniem zależności i pytaniami rekrutacyjnymi dla .NET 10. - Published: 2026-08-24 - Updated: 2026-08-24 - Author: Anthony Fillion-Maillet - Reading time: 5 min --- Clean Code Architecture w C# łączy zasady Czystego Kodu Roberta C. Martina ze wzorcem Clean Architecture, tworząc łatwe w utrzymaniu, testowalne i skalowalne aplikacje .NET. Rozmowy kwalifikacyjne coraz częściej koncentrują się na tych koncepcjach, ponieważ ujawniają sposób myślenia kandydata o projektowaniu oprogramowania wykraczający poza samo działanie kodu. > **Wskazówka Rekrutacyjna** > > Kiedy padają pytania o Clean Architecture, rekruterzy oczekują wyjaśnienia zasady zależności: zależności kodu źródłowego wskazują do wewnątrz, w kierunku polityk wyższego poziomu. Warstwa Domain nie wie nic o Infrastructure, a nie odwrotnie. ## Zasady SOLID jako Fundament Czystego Kodu C# Zasady SOLID stanowią kręgosłup Clean Code w C#. [Oficjalna dokumentacja Microsoft dotycząca podstaw .NET](https://learn.microsoft.com/en-us/dotnet/fundamentals/) zaleca te wzorce dla aplikacji korporacyjnych. Każda zasada rozwiązuje konkretny problem związany z utrzymaniem kodu. **Zasada Pojedynczej Odpowiedzialności (SRP)**: Klasa ma jeden powód do zmiany. Poniższy `OrderService` zajmuje się wyłącznie przetwarzaniem zamówień, delegując persystencję i powiadomienia do oddzielnych komponentów. ```csharp // OrderService.cs public class OrderService { private readonly IOrderRepository _orderRepository; private readonly INotificationService _notificationService; public OrderService( IOrderRepository orderRepository, INotificationService notificationService) { _orderRepository = orderRepository; _notificationService = notificationService; } public async Task CreateOrderAsync(CreateOrderRequest request) { // Validates and creates the domain entity var order = Order.Create(request.CustomerId, request.Items); // Persists through the repository abstraction await _orderRepository.AddAsync(order); // Notifies through a separate service await _notificationService.SendOrderConfirmationAsync(order); return order; } } ``` **Zasada Otwarte/Zamknięte (OCP)**: Klasy pozostają otwarte na rozszerzenie, ale zamknięte na modyfikację. Nowe metody płatności wymagają nowych klas, a nie zmian w istniejących. ```csharp // IPaymentProcessor.cs public interface IPaymentProcessor { string PaymentMethod { get; } Task ProcessAsync(Payment payment); } // StripePaymentProcessor.cs public class StripePaymentProcessor : IPaymentProcessor { public string PaymentMethod => "Stripe"; public async Task ProcessAsync(Payment payment) { // Stripe-specific implementation var charge = await _stripeClient.CreateChargeAsync(payment.Amount); return new PaymentResult(charge.Id, charge.Status == "succeeded"); } } // PaymentService.cs public class PaymentService { private readonly IEnumerable _processors; public PaymentService(IEnumerable processors) { _processors = processors; } public async Task ProcessPaymentAsync( Payment payment, string method) { var processor = _processors .FirstOrDefault(p => p.PaymentMethod == method) ?? throw new NotSupportedException($"Payment method {method} not supported"); return await processor.ProcessAsync(payment); } } ``` Dodanie obsługi PayPal oznacza dodanie klasy `PayPalPaymentProcessor`. `PaymentService` pozostaje niezmieniony. ## Cztery Warstwy Clean Architecture w .NET Clean Architecture organizuje kod w koncentryczne warstwy. [Repozytorium Clean Architecture autorstwa Jasona Taylora](https://github.com/jasontaylordev/CleanArchitecture) dostarcza szeroko stosowany szablon .NET. Każda warstwa ma jasno określone odpowiedzialności, a zależności płyną do wewnątrz. | Warstwa | Odpowiedzialność | Zależności | |---------|------------------|------------| | Domain | Encje, obiekty wartości, zdarzenia domenowe | Brak | | Application | Przypadki użycia, DTO, interfejsy | Domain | | Infrastructure | Baza danych, zewnętrzne API, system plików | Application, Domain | | Presentation | Kontrolery, widoki, endpointy API | Application | ```csharp // Domain/Entities/Customer.cs public class Customer { public Guid Id { get; private set; } public string Email { get; private set; } public CustomerStatus Status { get; private set; } private Customer() { } // EF Core constructor public static Customer Create(string email) { if (string.IsNullOrWhiteSpace(email)) throw new DomainException("Email cannot be empty"); return new Customer { Id = Guid.NewGuid(), Email = email.ToLowerInvariant(), Status = CustomerStatus.Active }; } public void Deactivate() { if (Status == CustomerStatus.Inactive) throw new DomainException("Customer already inactive"); Status = CustomerStatus.Inactive; } } ``` Encja Domain enkapsuluje reguły biznesowe. Waliduje własne niezmienniki i eksponuje zachowanie poprzez metody, nie settery. > **Dlaczego Prywatne Settery?** > > Prywatne settery uniemożliwiają zewnętrznemu kodowi wprowadzenie encji w nieprawidłowy stan. Metoda `Deactivate()` wymusza regułę, że nieaktywni klienci nie mogą być ponownie dezaktywowani. Ten wzorzec pojawia się często w implementacjach Domain-Driven Design i Clean Architecture. ## Wzorce Wstrzykiwania Zależności dla Clean Architecture Wstrzykiwanie Zależności (DI) umożliwia odwrócenie zależności wymagane przez Clean Architecture. .NET 10 zawiera wbudowany kontener DI obsługujący wstrzykiwanie przez konstruktor, scoped lifetimes i keyed services wprowadzone w .NET 8. ```csharp // Program.cs (Minimal API) var builder = WebApplication.CreateBuilder(args); // Domain services - typically transient builder.Services.AddTransient(); // Application services - scoped per request builder.Services.AddScoped(); // Infrastructure - scoped to share DbContext builder.Services.AddScoped(); builder.Services.AddScoped(); // External clients - singleton with HttpClient pooling builder.Services.AddHttpClient(client => { client.BaseAddress = new Uri("https://api.stripe.com/v1/"); }); // Keyed services for multiple implementations (.NET 8+) builder.Services.AddKeyedScoped("email"); builder.Services.AddKeyedScoped("sms"); var app = builder.Build(); ``` Warstwa Application definiuje interfejsy. Warstwa Infrastructure je implementuje. Warstwa Presentation (lub composition root) łączy wszystko razem. ## Wzorzec Repository z Entity Framework Core 9 Wzorzec Repository abstrahuje dostęp do danych za interfejsami zorientowanymi na domenę. EF Core 9, dostarczany z .NET 10, zapewnia bazowy ORM. Zaawansowane wzorce EF Core opisano w [przewodniku wydajności EF Core SharpSkill](/blog/dotnet/ef-core-performance-best-practices). ```csharp // Application/Interfaces/IOrderRepository.cs public interface IOrderRepository { Task GetByIdAsync(Guid id, CancellationToken ct = default); Task> GetByCustomerAsync(Guid customerId, CancellationToken ct = default); Task AddAsync(Order order, CancellationToken ct = default); void Update(Order order); } // Infrastructure/Repositories/OrderRepository.cs public class OrderRepository : IOrderRepository { private readonly ApplicationDbContext _context; public OrderRepository(ApplicationDbContext context) { _context = context; } public async Task GetByIdAsync(Guid id, CancellationToken ct = default) { return await _context.Orders .Include(o => o.Items) .FirstOrDefaultAsync(o => o.Id == id, ct); } public async Task> GetByCustomerAsync( Guid customerId, CancellationToken ct = default) { return await _context.Orders .Where(o => o.CustomerId == customerId) .OrderByDescending(o => o.CreatedAt) .ToListAsync(ct); } public async Task AddAsync(Order order, CancellationToken ct = default) { await _context.Orders.AddAsync(order, ct); } public void Update(Order order) { _context.Orders.Update(order); } } ``` Repository zwraca encje domenowe, nie DTO. Mapowanie na DTO odbywa się w warstwie Application, zachowując czystość Domain. ## Testowanie Jednostkowe Komponentów Clean Architecture Clean Architecture ułatwia testowanie, ponieważ zależności są wstrzykiwane przez interfejsy. [xUnit](https://xunit.net/) i [NSubstitute](https://nsubstitute.github.io/) dostarczają framework testowy i bibliotekę mockowania dla .NET. ```csharp // OrderServiceTests.cs public class OrderServiceTests { private readonly IOrderRepository _orderRepository; private readonly INotificationService _notificationService; private readonly OrderService _sut; public OrderServiceTests() { _orderRepository = Substitute.For(); _notificationService = Substitute.For(); _sut = new OrderService(_orderRepository, _notificationService); } [Fact] public async Task CreateOrderAsync_ValidRequest_PersistsAndNotifies() { // Arrange var request = new CreateOrderRequest( CustomerId: Guid.NewGuid(), Items: new[] { new OrderItemDto("SKU-001", 2, 29.99m) }); // Act var order = await _sut.CreateOrderAsync(request); // Assert await _orderRepository.Received(1).AddAsync(Arg.Any()); await _notificationService.Received(1) .SendOrderConfirmationAsync(Arg.Is(o => o.Id == order.Id)); } [Fact] public async Task CreateOrderAsync_EmptyItems_ThrowsDomainException() { // Arrange var request = new CreateOrderRequest( CustomerId: Guid.NewGuid(), Items: Array.Empty()); // Act & Assert await Assert.ThrowsAsync( () => _sut.CreateOrderAsync(request)); } } ``` Testy weryfikują zachowanie, nie implementację. `_sut` (system under test) współdziała z mockami. Asercje sprawdzają, czy właściwe metody zostały wywołane z właściwymi argumentami. ## Pytania Rekrutacyjne dotyczące Clean Architecture C# Rozmowy techniczne badają zrozumienie Clean Architecture na wielu poziomach. Te pytania pojawiają się na rozmowach dla seniorów .NET. Więcej materiałów przygotowawczych znajduje się w [module pytań rekrutacyjnych Clean Architecture SharpSkill](/technologies/dotnet/interview-questions/clean-architecture). > **Częsta Pułapka Rekrutacyjna** > > Kandydaci często mylą Clean Architecture z architekturą N-warstwową. Kluczowa różnica: w Clean Architecture zależności wskazują do wewnątrz, w kierunku Domain. W N-tier każda warstwa zależy od warstwy poniżej, czyniąc Domain zależnym od Infrastructure. **P: Czym Clean Architecture różni się od tradycyjnej architektury warstwowej?** Tradycyjna architektura warstwowa sprawia, że każda warstwa zależy od warstwy poniżej: Presentation zależy od Business Logic, która zależy od Data Access. Clean Architecture odwraca to: Domain nie ma zależności, Application zależy od Domain, a Infrastructure zależy od obu. To odwrócenie oznacza, że technologia bazy danych może się zmienić bez dotykania reguł biznesowych. **P: Kiedy nie użyłbyś Clean Architecture?** Clean Architecture dodaje pośrednictwo. Dla aplikacji opartych głównie na CRUD bez złożonych reguł biznesowych, narzut przewyższa korzyści. Proste API, które bezpośrednio proxy'uje tabele bazy danych, nie potrzebuje czterech warstw. Wartość pojawia się, gdy złożoność logiki biznesowej uzasadnia separację. **P: Jak obsługujesz zagadnienia przekrojowe jak logowanie i cache'owanie?** Dwa wzorce sprawdzają się dobrze: wzorzec dekoratora i middleware. Dekorator cache'ujący opakowuje interfejs repozytorium, implementując ten sam interfejs, dodając logikę cache. Logowanie zazwyczaj używa middleware lub interceptora DI, który opakowuje wywołania serwisów bez zanieczyszczania logiki biznesowej. ```csharp // CachingOrderRepository.cs (Decorator pattern) public class CachingOrderRepository : IOrderRepository { private readonly IOrderRepository _inner; private readonly IDistributedCache _cache; public CachingOrderRepository( IOrderRepository inner, IDistributedCache cache) { _inner = inner; _cache = cache; } public async Task GetByIdAsync(Guid id, CancellationToken ct = default) { var cacheKey = $"order:{id}"; var cached = await _cache.GetStringAsync(cacheKey, ct); if (cached is not null) return JsonSerializer.Deserialize(cached); var order = await _inner.GetByIdAsync(id, ct); if (order is not null) { await _cache.SetStringAsync( cacheKey, JsonSerializer.Serialize(order), new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5) }, ct); } return order; } // Other methods delegate to _inner } ``` **P: Jak strukturyzujesz walidację w Clean Architecture?** Walidacja zachodzi na dwóch poziomach. Walidacja domenowa (niezmienniki) należy do encji: Zamówienie nie może mieć zero pozycji. Walidacja aplikacyjna (walidacja danych wejściowych) należy do handlerów komend lub walidatorów: żądanie musi zawierać prawidłowy ID klienta. FluentValidation dobrze integruje się dla walidacji danych wejściowych, podczas gdy walidacja domenowa pozostaje w konstruktorach i metodach encji. ## Organizacja Kodu i Konwencje Nazewnictwa Struktura projektu komunikuje architekturę. Standardowy szablon Clean Architecture organizuje projekty według warstw. ``` src/ ├── MyApp.Domain/ │ ├── Entities/ │ ├── ValueObjects/ │ ├── Events/ │ └── Exceptions/ ├── MyApp.Application/ │ ├── Common/ │ │ ├── Interfaces/ │ │ └── Behaviors/ │ ├── Orders/ │ │ ├── Commands/ │ │ ├── Queries/ │ │ └── EventHandlers/ │ └── Customers/ ├── MyApp.Infrastructure/ │ ├── Persistence/ │ ├── Services/ │ └── Configuration/ └── MyApp.WebApi/ ├── Controllers/ ├── Filters/ └── Middleware/ ``` Pionowe wycinki (Orders, Customers) grupują powiązane przypadki użycia. Ta organizacja skaluje się lepiej niż poziome wycinki (Commands, Queries) wraz ze wzrostem aplikacji. ## Kwestie Wydajnościowe w Clean Architecture Abstrakcja ma swój koszt. Każde wywołanie interfejsu dodaje pośrednictwo. Te praktyki minimalizują narzut, zachowując testowalność. **Używaj rekordów dla DTO**: Rekordy generują efektywne implementacje `Equals` i `GetHashCode`. Są domyślnie niemutowalne, zapobiegając przypadkowej mutacji. ```csharp // Application/Orders/Queries/OrderDto.cs public record OrderDto( Guid Id, Guid CustomerId, IReadOnlyList Items, decimal Total, DateTime CreatedAt); public record OrderItemDto( string Sku, int Quantity, decimal UnitPrice); ``` **Unikaj nadmiernej abstrakcji**: Nie każda klasa potrzebuje interfejsu. Abstrakcja zewnętrznych zależności (baza danych, klienty HTTP, system plików) jest konieczna. Wewnętrzne serwisy domenowe z jedną implementacją rzadko potrzebują interfejsów. **Profiluj przed optymalizacją**: Narzut warstw Clean Architecture jest zazwyczaj znikomy w porównaniu z operacjami I/O. Zapytanie do bazy danych trwające 50ms przyćmiewa mikrosekundy spędzone na dyspozycji metod. ## Stosowanie Zasad Clean Code do Metod C# Clean Code koncentruje się na czytelności na poziomie metod i klas. Te praktyki obowiązują niezależnie od wzorca architektonicznego. **Metody robią jedną rzecz**: Metoda nazwana `ProcessOrderAndSendEmail` narusza SRP. Podziel ją na `ProcessOrder` i `SendOrderConfirmation`. **Znaczące nazwy**: `CalculateOrderTotal` komunikuje intencję. `DoCalculation` nie. Nazwy zmiennych podlegają tej samej regule: `customerOrders` zamiast `list`. **Małe metody**: Jeśli metoda przekracza 20 linii, prawdopodobnie robi za dużo. Wyodrębnij metody pomocnicze z opisowymi nazwami. ```csharp // Before: Long method doing multiple things public decimal CalculateInvoiceTotal(Invoice invoice) { decimal subtotal = 0; foreach (var item in invoice.Items) { subtotal += item.Quantity * item.UnitPrice; } decimal discount = 0; if (invoice.Customer.IsPreferred) { discount = subtotal * 0.1m; } decimal tax = (subtotal - discount) * 0.2m; return subtotal - discount + tax; } // After: Small methods with single responsibilities public decimal CalculateInvoiceTotal(Invoice invoice) { var subtotal = CalculateSubtotal(invoice.Items); var discount = CalculateDiscount(subtotal, invoice.Customer); var tax = CalculateTax(subtotal - discount); return subtotal - discount + tax; } private decimal CalculateSubtotal(IEnumerable items) => items.Sum(item => item.Quantity * item.UnitPrice); private decimal CalculateDiscount(decimal subtotal, Customer customer) => customer.IsPreferred ? subtotal * 0.1m : 0; private decimal CalculateTax(decimal taxableAmount) => taxableAmount * 0.2m; ``` Zrefaktoryzowana wersja czyta się jak podsumowanie logiki biznesowej. Każda metoda pomocnicza może być testowana niezależnie. ## Kluczowe Wnioski dla Clean Code Architecture w C# - Zasada zależności: zależności kodu wskazują do wewnątrz, od Infrastructure w kierunku Domain, nigdy na zewnątrz - Zasady SOLID kierują projektowaniem klas: pojedyncza odpowiedzialność, otwartość na rozszerzenie, podstawienie Liskov, segregacja interfejsów, odwrócenie zależności - Cztery warstwy separują odpowiedzialności: Domain (encje), Application (przypadki użycia), Infrastructure (systemy zewnętrzne), Presentation (API/UI) - Wstrzykiwanie zależności łączy warstwy w composition root, zazwyczaj w `Program.cs` - Wzorzec Repository abstrahuje dostęp do danych za interfejsami zorientowanymi na domenę, które zwracają encje, nie DTO - Testy jednostkowe weryfikują zachowanie poprzez mockowanie interfejsów, walidując że właściwe metody otrzymują właściwe argumenty - Clean Code na poziomie metod: małe metody, znaczące nazwy, pojedyncze odpowiedzialności - Koszt wydajnościowy abstrakcji jest zazwyczaj znikomy w porównaniu z operacjami I/O; profiluj przed optymalizacją - Pytania rekrutacyjne badają zrozumienie zasady zależności, kompromisów i praktycznych wzorców jak dekoratory dla zagadnień przekrojowych --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/dotnet/clean-code-architecture-csharp-guide-interview-questions-2026