Clean Code Architecture C#: Kapsamlı Rehber ve Mülakat Soruları 2026

C# dilinde Clean Code ve Clean Architecture kavramlarını SOLID prensipleri, bağımlılık enjeksiyonu ve .NET 10 mülakat soruları ile öğrenin.

Clean Code Architecture C#: Kapsamlı Rehber ve Mülakat Soruları 2026

C# dilinde Clean Code Architecture, Robert C. Martin'in Clean Code prensiplerini Clean Architecture deseni ile birleştirerek bakımı kolay, test edilebilir ve ölçeklenebilir .NET uygulamaları oluşturur. Teknik mülakatlar bu kavramlara giderek daha fazla odaklanmaktadır çünkü bir adayın yazılım tasarımı hakkındaki düşünce yapısını sadece kodun çalışmasının ötesinde ortaya koyarlar.

Mülakat İpucu

Clean Architecture hakkında sorulduğunda, mülakatçılar adayların bağımlılık kuralını açıklamasını bekler: kaynak kod bağımlılıkları içe doğru, daha yüksek seviyeli politikalara doğru işaret eder. Domain katmanı Infrastructure hakkında hiçbir şey bilmez, tam tersi değil.

SOLID Prensipleri: Temiz C# Kodunun Temeli

SOLID prensipleri, C# dilinde Clean Code'un omurgasını oluşturur. Microsoft'un resmi .NET temel dokümantasyonu kurumsal uygulamalar için bu desenleri önerir. Her prensip belirli bir bakım sorununu ele alır.

Tek Sorumluluk Prensibi (SRP): Bir sınıfın değişmesi için tek bir neden olmalıdır. Aşağıdaki OrderService yalnızca sipariş işleme ile ilgilenir, kalıcılık ve bildirimleri ayrı bileşenlere devreder.

OrderService.cscsharp
public class OrderService
{
    private readonly IOrderRepository _orderRepository;
    private readonly INotificationService _notificationService;

    public OrderService(
        IOrderRepository orderRepository,
        INotificationService notificationService)
    {
        _orderRepository = orderRepository;
        _notificationService = notificationService;
    }

    public async Task<Order> 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;
    }
}

Açık/Kapalı Prensibi (OCP): Sınıflar genişletmeye açık ancak değişikliğe kapalı kalmalıdır. Yeni ödeme yöntemleri mevcut sınıflarda değişiklik değil, yeni sınıflar gerektirir.

IPaymentProcessor.cscsharp
public interface IPaymentProcessor
{
    string PaymentMethod { get; }
    Task<PaymentResult> ProcessAsync(Payment payment);
}

// StripePaymentProcessor.cs
public class StripePaymentProcessor : IPaymentProcessor
{
    public string PaymentMethod => "Stripe";
    
    public async Task<PaymentResult> 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<IPaymentProcessor> _processors;

    public PaymentService(IEnumerable<IPaymentProcessor> processors)
    {
        _processors = processors;
    }

    public async Task<PaymentResult> 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);
    }
}

PayPal desteği eklemek PayPalPaymentProcessor sınıfı eklemek anlamına gelir. PaymentService değişmeden kalır.

.NET'te Clean Architecture'ın Dört Katmanı

Clean Architecture, kodu eşmerkezli katmanlar halinde düzenler. Jason Taylor'ın Clean Architecture deposu yaygın olarak benimsenen bir .NET şablonu sağlar. Her katmanın açık sorumlulukları vardır ve bağımlılıklar içe doğru akar.

KatmanSorumlulukBağımlılıklar
DomainVarlıklar, değer nesneleri, domain olaylarıYok
ApplicationKullanım senaryoları, DTO'lar, arayüzlerDomain
InfrastructureVeritabanı, harici API'ler, dosya sistemiApplication, Domain
PresentationController'lar, görünümler, API endpoint'leriApplication
Domain/Entities/Customer.cscsharp
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;
    }
}

Domain varlığı iş kurallarını kapsüller. Kendi değişmezlerini doğrular ve davranışı setter'lar yerine metotlar aracılığıyla sunar.

Neden Private Setter'lar?

Private setter'lar, dış kodun varlıkları geçersiz durumlara sokmasını engeller. Deactivate() metodu, pasif müşterilerin tekrar pasifleştirilemeyeceği kuralını uygular. Bu desen, Domain-Driven Design ve Clean Architecture implementasyonlarında sıkça görülür.

Clean Architecture için Bağımlılık Enjeksiyonu Desenleri

Bağımlılık Enjeksiyonu (DI), Clean Architecture'ın gerektirdiği bağımlılık tersine çevirmeyi sağlar. .NET 10, constructor enjeksiyonu, scoped lifetime'lar ve .NET 8'de tanıtılan keyed service'leri destekleyen yerleşik bir DI konteyneri içerir.

Program.cs (Minimal API)csharp
var builder = WebApplication.CreateBuilder(args);

// Domain services - typically transient
builder.Services.AddTransient<IOrderValidator, OrderValidator>();

// Application services - scoped per request
builder.Services.AddScoped<IOrderService, OrderService>();

// Infrastructure - scoped to share DbContext
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();

// External clients - singleton with HttpClient pooling
builder.Services.AddHttpClient<IPaymentGateway, StripeGateway>(client =>
{
    client.BaseAddress = new Uri("https://api.stripe.com/v1/");
});

// Keyed services for multiple implementations (.NET 8+)
builder.Services.AddKeyedScoped<INotificationService, EmailNotificationService>("email");
builder.Services.AddKeyedScoped<INotificationService, SmsNotificationService>("sms");

var app = builder.Build();

Application katmanı arayüzleri tanımlar. Infrastructure katmanı bunları uygular. Presentation katmanı (veya composition root) her şeyi bir araya getirir.

Entity Framework Core 9 ile Repository Deseni

Repository deseni, veri erişimini domain odaklı arayüzlerin arkasına soyutlar. .NET 10 ile gelen EF Core 9, temel ORM'i sağlar. Gelişmiş EF Core desenleri için SharpSkill EF Core performans rehberine bakılabilir.

Application/Interfaces/IOrderRepository.cscsharp
public interface IOrderRepository
{
    Task<Order?> GetByIdAsync(Guid id, CancellationToken ct = default);
    Task<IReadOnlyList<Order>> 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<Order?> GetByIdAsync(Guid id, CancellationToken ct = default)
    {
        return await _context.Orders
            .Include(o => o.Items)
            .FirstOrDefaultAsync(o => o.Id == id, ct);
    }

    public async Task<IReadOnlyList<Order>> 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, DTO'lar değil domain varlıkları döndürür. DTO'lara eşleme Application katmanında gerçekleşir ve Domain'in saflığı korunur.

.NET mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

Clean Architecture Bileşenlerinin Birim Testi

Clean Architecture, bağımlılıklar arayüzler aracılığıyla enjekte edildiğinden test etmeyi kolaylaştırır. xUnit ve NSubstitute, .NET için test framework'ü ve mocking kütüphanesi sağlar.

OrderServiceTests.cscsharp
public class OrderServiceTests
{
    private readonly IOrderRepository _orderRepository;
    private readonly INotificationService _notificationService;
    private readonly OrderService _sut;

    public OrderServiceTests()
    {
        _orderRepository = Substitute.For<IOrderRepository>();
        _notificationService = Substitute.For<INotificationService>();
        _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<Order>());
        await _notificationService.Received(1)
            .SendOrderConfirmationAsync(Arg.Is<Order>(o => o.Id == order.Id));
    }

    [Fact]
    public async Task CreateOrderAsync_EmptyItems_ThrowsDomainException()
    {
        // Arrange
        var request = new CreateOrderRequest(
            CustomerId: Guid.NewGuid(),
            Items: Array.Empty<OrderItemDto>());

        // Act & Assert
        await Assert.ThrowsAsync<DomainException>(
            () => _sut.CreateOrderAsync(request));
    }
}

Testler, implementasyonu değil davranışı doğrular. _sut (test edilen sistem) mock'larla etkileşime girer. Assertion'lar doğru metotların doğru argümanlarla çağrılıp çağrılmadığını kontrol eder.

Clean Architecture C# Mülakat Soruları

Teknik mülakatlar, Clean Architecture anlayışını birden fazla düzeyde sorgular. Bu sorular kıdemli .NET mülakatlarında karşılaşılır. Daha fazla hazırlık materyali için SharpSkill Clean Architecture mülakat modülüne bakılabilir.

Yaygın Mülakat Tuzağı

Adaylar sıklıkla Clean Architecture'ı N-tier mimari ile karıştırır. Temel fark: Clean Architecture'da bağımlılıklar içe doğru, Domain'e doğru işaret eder. N-tier'da her katman altındaki katmana bağımlıdır, bu da Domain'i Infrastructure'a bağımlı kılar.

S: Clean Architecture geleneksel katmanlı mimariden nasıl farklıdır?

Geleneksel katmanlı mimari, her katmanı altındaki katmana bağımlı kılar: Presentation, Business Logic'e bağımlıdır; Business Logic da Data Access'e bağımlıdır. Clean Architecture bunu tersine çevirir: Domain'in hiç bağımlılığı yoktur, Application Domain'e bağımlıdır ve Infrastructure her ikisine de bağımlıdır. Bu tersine çevirme, veritabanı teknolojisinin iş kurallarına dokunmadan değişebileceği anlamına gelir.

S: Clean Architecture'ı ne zaman kullanmazsınız?

Clean Architecture dolaylama ekler. Karmaşık iş kuralları olmayan CRUD ağırlıklı uygulamalar için yük, faydayı aşar. Veritabanı tablolarını doğrudan proxy'leyen basit bir API'nin dört katmana ihtiyacı yoktur. Değer, iş mantığı karmaşıklığı ayrımı haklı kıldığında ortaya çıkar.

S: Loglama ve önbellekleme gibi kesişen ilgileri nasıl ele alırsınız?

İki desen iyi çalışır: dekoratör deseni ve middleware. Önbellek dekoratörü, repository arayüzünü sarar, aynı arayüzü uygulayarak önbellek mantığı ekler. Loglama tipik olarak iş mantığını kirletmeden servis çağrılarını saran middleware veya DI interceptor kullanır.

CachingOrderRepository.cs (Decorator pattern)csharp
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<Order?> 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<Order>(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
}

S: Clean Architecture'da doğrulamayı nasıl yapılandırırsınız?

Doğrulama iki seviyede gerçekleşir. Domain doğrulaması (değişmezler) varlıklara aittir: Bir Sipariş sıfır öğeye sahip olamaz. Uygulama doğrulaması (girdi doğrulaması) komut işleyicilere veya doğrulayıcılara aittir: istek geçerli bir müşteri ID'si içermelidir. FluentValidation girdi doğrulaması için iyi entegre olurken, domain doğrulaması varlık constructor'larında ve metotlarında kalır.

Kod Organizasyonu ve Adlandırma Kuralları

Proje yapısı mimariyi iletir. Standart Clean Architecture şablonu, projeleri katmanlara göre düzenler.

text
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/

Dikey dilimler (Orders, Customers) ilgili kullanım senaryolarını gruplar. Bu organizasyon, uygulama büyüdükçe yatay dilimlerden (Commands, Queries) daha iyi ölçeklenir.

Clean Architecture'da Performans Değerlendirmeleri

Soyutlamanın bir maliyeti vardır. Her arayüz çağrısı dolaylama ekler. Bu pratikler, test edilebilirliği korurken yükü en aza indirir.

DTO'lar için record kullanın: Record'lar verimli Equals ve GetHashCode implementasyonları oluşturur. Varsayılan olarak değişmezdirler ve kazara mutasyonu önlerler.

Application/Orders/Queries/OrderDto.cscsharp
public record OrderDto(
    Guid Id,
    Guid CustomerId,
    IReadOnlyList<OrderItemDto> Items,
    decimal Total,
    DateTime CreatedAt);

public record OrderItemDto(
    string Sku,
    int Quantity,
    decimal UnitPrice);

Aşırı soyutlamadan kaçının: Her sınıfın bir arayüze ihtiyacı yoktur. Dış bağımlılıkları (veritabanı, HTTP istemcileri, dosya sistemi) soyutlayın. Tek implementasyonlu dahili domain servisleri nadiren arayüzlere ihtiyaç duyar.

Optimize etmeden önce profil çıkarın: Clean Architecture katmanlarının yükü, I/O işlemlerine kıyasla genellikle ihmal edilebilir düzeydedir. 50ms süren bir veritabanı sorgusu, metot dispatch'te harcanan mikrosaniyeleri gölgede bırakır.

Clean Code Prensiplerini C# Metotlarına Uygulama

Clean Code, metot ve sınıf düzeyinde okunabilirliğe odaklanır. Bu pratikler mimari desenden bağımsız olarak geçerlidir.

Metotlar tek bir şey yapar: ProcessOrderAndSendEmail adlı bir metot SRP'yi ihlal eder. ProcessOrder ve SendOrderConfirmation olarak ayırın.

Anlamlı isimler: CalculateOrderTotal amacı iletir. DoCalculation iletmez. Değişken isimleri de aynı kurala tabidir: list yerine customerOrders.

Küçük metotlar: Bir metot 20 satırı aşıyorsa, muhtemelen çok fazla şey yapıyordur. Açıklayıcı isimlerle yardımcı metotlar çıkarın.

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<InvoiceItem> 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;

Yeniden düzenlenen versiyon, iş mantığının bir özeti gibi okunur. Her yardımcı metot bağımsız olarak test edilebilir.

Pratik yapmaya başla!

Mülakat simülatörleri ve teknik testlerle bilgini test et.

C# Clean Code Architecture için Temel Çıkarımlar

  • Bağımlılık kuralı: kod bağımlılıkları içe doğru, Infrastructure'dan Domain'e doğru işaret eder, asla dışa doğru değil
  • SOLID prensipleri sınıf tasarımına rehberlik eder: tek sorumluluk, genişletmeye açıklık, Liskov yerine koyma, arayüz ayrımı, bağımlılık tersine çevirme
  • Dört katman sorumlulukları ayırır: Domain (varlıklar), Application (kullanım senaryoları), Infrastructure (dış sistemler), Presentation (API/UI)
  • Bağımlılık enjeksiyonu, katmanları composition root'ta birbirine bağlar, genellikle Program.cs'de
  • Repository deseni, veri erişimini DTO'lar değil varlıklar döndüren domain odaklı arayüzlerin arkasına soyutlar
  • Birim testleri, arayüzleri mock'layarak davranışı doğrular, doğru metotların doğru argümanları aldığını valide eder
  • Metot düzeyinde Clean Code: küçük metotlar, anlamlı isimler, tek sorumluluklar
  • Soyutlamanın performans maliyeti, I/O işlemlerine kıyasla genellikle ihmal edilebilir düzeydedir; optimize etmeden önce profil çıkarın
  • Mülakat soruları, bağımlılık kuralı, trade-off'lar ve kesişen ilgiler için dekoratörler gibi pratik desenlerin anlaşılmasını sorgular
Günün meydan okuması

.NET kodundaki hatayı bulabilir misin?

Gerçek bir kod parçası, gizli bir hata, günde bir deneme. Denemek için hesap gerekmez.

Anthony Fillion-Maillet

Yazan:

Anthony Fillion-Maillet

SharpSkill kurucusu

10 yılı aşkın süredir fullstack geliştirici. SharpSkill’i yönetiyor ve burada yayımlanan her şeyden sorumlu.

24 Ağustos 2026 tarihinde güncellendi

Paylaş

İlgili makaleler