Giriş

Yazılım projelerinin uzun ömürlü, sürdürülebilir ve değişime dirençli olması, yalnızca güzel kod yazmakla değil; temeli sağlam bir mimari yapı ve evrensel prensiplerle mümkündür.

Günümüzde en çok tercih edilen iki yaklaşım:

  • Clean Architecture: Uygulama katmanlarının sorumlulukları net ayrılmış, bağımlılıkları kontrollü şekilde merkeze doğru ilerleyen mimari model.
  • SOLID Prensipleri: Nesne yönelimli programlamanın 5 temel ilkesi ile modüler, okunabilir ve test edilebilir kod üretme standardı.

Bu yazıda, her iki yaklaşımı ayrıntılı olarak ele alacak;
– Hangi sorunları çözdüğünü,
– Hangi projelerde neden tercih edildiğini,
– Hangi katmanda hangi SOLID ilkesinin en iyi nasıl uygulandığını,
– Gerçek kod örnekleri, avantajlar/dezavantajlar ve mimari tuzaklarıyla birlikte detaylandıracağız.

1. Clean Architecture: Temel Yapı ve Katmanlar

1.1 Clean Architecture’ın Amacı ve Temel Felsefesi

  • Tüm uygulama merkezinde “iş kurallarını” tutar (domain logic).
  • Dış dünyadan (UI, veritabanı, dosya, API, 3rd party) gelen her şey, bir veya daha fazla soyutlama (interface) katmanından geçer.
  • En içte entity/domain, onun dışında use case/application, bir üstte interface adapters, en dışta ise frameworks & drivers (veritabanı, UI, servisler) vardır.
  • Bağımlılık yönü: Her zaman dıştan içe (Dependency Rule).

1.2 Clean Architecture Katmanları

Her bir katmanın rolü, örnek işlevleri ve bağımlılık ilişkileri:

a) Entities (Domain Layer)

  • Görev: İş kuralları, entity ve value object’ler.
  • Bağımlılık: Hiçbir dış katmana, framework’e veya altyapıya bağımlı değildir.
  • Örnek: Sipariş, ürün, müşteri gibi kavramlar ve bunların kuralları.

b) Use Cases (Application Layer)

  • Görev: Akışları ve domain’in “nasıl kullanılacağını” yönetir.
  • Bağımlılık: Domain katmanına; dış katmanlara asla doğrudan değil, interface üzerinden bağımlı.
  • Örnek: Sipariş verme, müşteri kayıt, şifre sıfırlama use case’leri.

c) Interface Adapters

  • Görev: UI’dan veya dış kaynaktan gelen veriyi uygulama katmanına çevirir; DTO, mapping, presenter, controller.
  • Bağımlılık: Uygulama katmanına.
  • Örnek: Web API Controller, REST API, DTO to Entity/Entity to DTO mapping.

d) Frameworks & Drivers

  • Görev: ORM, dosya sistemi, harici API’ler, 3rd party kütüphaneler, UI framework’leri.
  • Bağımlılık: Sadece interface adapters’a.
  • Örnek: Entity Framework, Dapper, ASP.NET MVC, Redis, Kafka, Mailgun entegrasyonu.

1.3 Clean Architecture’da Katmanlar Arası Bağımlılık Akışı

  • Bağımlılıklar her zaman merkeze doğru yönelir (yani UI, UseCase’e; UseCase, Domain’e bağımlı).
  • Domain ve Application katmanları asla Framework, ORM, veri kaynağı veya 3rd party kütüphaneye bağımlı olmaz.
  • Altyapı bileşenleri (ör. repository implementasyonu), sadece interface üzerinden iç katmanlara entegre olur.

2. SOLID Prensipleri: Derinlemesine Analiz ve Uygulama

2.1 Single Responsibility Principle (SRP)

Her sınıf veya modülün yalnızca tek bir sorumluluğu olmalı. Sık ihlal edilen bir ilke olup, kodda bağımsız olarak değişen nedenlerin bir arada bulunmasını engeller.

Clean Architecture’daki Pratik:

  • Her UseCase bir sınıf (ör. RegisterOrderUseCase)
  • Controller’ın görevi sadece “isteği almak ve yönlendirmek”
  • Entity’nin görevi sadece domain kurallarını barındırmak

C# SRP İyi Örnek:

public class Order
{
    public void AddItem(Product product) { /* Sadece iş mantığı */ }
}
// Kötü örnek: Email gönderimini veya dosya yazmayı buraya koymak!

2.2 Open/Closed Principle (OCP)

Sınıflar değişime kapalı, genişletmeye açık olmalı. Yani mevcut kodu değiştirmeden yeni işlev eklenebilmeli.

Clean Architecture’daki Pratik:

  • Yeni bir ödeme yöntemi eklemek için mevcut “Order” kodunu değiştirmek yerine yeni bir “IPaymentMethod” implementasyonu eklemek.
  • Entity’leri veya use case’leri değiştirmek yerine, genişletmek.

C# OCP Örneği:

public interface IPaymentMethod
{
    void Pay(Order order);
}
public class CreditCardPayment : IPaymentMethod { /* ... */ }
public class PaypalPayment : IPaymentMethod { /* ... */ }
// Order, yeni bir ödeme eklenince değişmez.

2.3 Liskov Substitution Principle (LSP)

Alt sınıflar, üst sınıfların yerine geçebilmeli. Hiçbir kod, alt sınıfı kullandığında hata almamalı.

Clean Architecture’daki Pratik:

  • Bağımlılıklar soyutlamalar üzerinden tanımlanır, alt sınıflar sorunsuzca yer değiştirir.

C# LSP Örneği:

public abstract class Notification
{
    public abstract void Send(string message);
}
public class EmailNotification : Notification
{
    public override void Send(string message) { /* ... */ }
}
public class SmsNotification : Notification
{
    public override void Send(string message) { /* ... */ }
}
// Kod, Notification üzerinden işlem yaptığı sürece alt sınıflar sorunsuz çalışır.

2.4 Interface Segregation Principle (ISP)

Büyük ve genel arayüzler yerine, ihtiyaca özel küçük arayüzler tercih edilmeli. Kullanıcılar ihtiyaç duymadığı metodlarla uğraşmamalı.

Clean Architecture’daki Pratik:

  • Repository arayüzlerinin gereksiz fonksiyonlarla dolu olmaması
  • Her kullanıcının ihtiyacı kadar arayüz kullanması

C# ISP Örneği:

public interface IReadOnlyRepository<T>
{
    T GetById(int id);
    IEnumerable<T> GetAll();
}

public interface IWriteRepository<T>
{
    void Add(T entity);
    void Remove(T entity);
}
// Sadece okuma veya sadece yazma işlemleri için ayrık interface

2.5 Dependency Inversion Principle (DIP)

Yüksek seviye modüller (use case/domain), düşük seviye modüllere (ORM, dosya, veri kaynağı) doğrudan bağımlı olmamalı. Her iki seviye de soyutlamalara (interface/abstract) bağımlı olmalı.

Clean Architecture’daki Pratik:

  • UseCase, repository interface’e bağımlı, uygulama ise bunu Dependency Injection ile gerçek altyapı implementasyonuna bağlar.
  • Altyapı değişse bile, domain ve application değişmez.

C# DIP Örneği:

public interface IOrderRepository
{
    Order GetById(int id);
    void Save(Order order);
}
// Altyapı implementasyonu (ör. EfOrderRepository) dışta, use case sadece interface’e bağımlı.

3. Katmanlar Arası Bağımlılık ve Akış Detayı

3.1 Tipik Clean Architecture Akışı

  1. Kullanıcı bir istek (ör. sipariş oluştur) gönderir.
  2. Controller isteği alır, veriyi DTO ile çevirir.
  3. Use Case çalışır; domain nesnelerini kullanır, repository veya servisleri arayüzden çağırır.
  4. Repository Implementasyonu (ör. EfCore) interface üzerinden çağrılır.
  5. Sonuç, presenter veya DTO ile kullanıcıya döner.

3.2 Bağımlılıkların Pratikte Yönetimi

  • Dependency Injection Container (örn. .NET Core DI) ile interface-implementasyon eşleştirmesi yapılır.
  • Mock implementasyonlarla test kolaylaşır.

4. Clean Architecture ile SOLID’in Projelere Katkısı

4.1 Sürdürülebilirlik

Kod büyüdükçe değişiklikler izole, geriye dönük hatalar azalır, bakım kolaylaşır.

4.2 Test Edilebilirlik

Her katman ve arayüz, kolayca “mock” ile izole edilerek test edilir. Örneğin altyapı olmadan domain ve application test edilebilir.

4.3 Refactoring ve Esneklik

Yeni gereksinimler için “yama” yapmak gerekmez; genişletme yoluyla ilerleme sağlanır.

4.4 Paralel Geliştirme

UI, domain, altyapı ve use case ekipleri birbirinden bağımsız çalışabilir.

5. Gelişmiş Kod Örnekleri ile Clean Architecture & SOLID

5.1 Use Case + Repository + Interface Adapter (C#)

// Domain Katmanı
public class Product
{
    public int Id { get; }
    public string Name { get; }
    public decimal Price { get; }
}

// Repository Arayüzü
public interface IProductRepository
{
    Product GetById(int id);
    void Add(Product product);
}

// Use Case
public class RegisterProductUseCase
{
    private readonly IProductRepository _repo;
    public RegisterProductUseCase(IProductRepository repo)
    {
        _repo = repo;
    }
    public void Execute(string name, decimal price)
    {
        var product = new Product(name, price);
        _repo.Add(product);
    }
}

// Infrastructure Katmanı
public class EfProductRepository : IProductRepository
{
    // EF Core ile implementasyon
}

// Controller Katmanı
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
    private readonly RegisterProductUseCase _useCase;
    public ProductsController(RegisterProductUseCase useCase)
    {
        _useCase = useCase;
    }

    [HttpPost]
    public IActionResult Create(ProductDto dto)
    {
        _useCase.Execute(dto.Name, dto.Price);
        return Ok();
    }
}

6. Katmanlar Arası Veri Aktarımı, DTO & Mapping Stratejileri

  • DTO (Data Transfer Object): UI ile uygulama katmanları arasında taşınır.
  • Mapper Katmanı: Automapper veya el ile mapping ile veri dönüşümleri sağlanır.
  • Domain nesneleri asla controller veya view’da kullanılmaz.
  • Adapter/Presenter’lar ile veri sunumuna uygun şekilde dönüştürülür.

clean-arc-harita

7. Clean Architecture ile SOLID’e Uygun Bir Projenin Organizasyonu (Dizin Yapısı Örneği)

/src
  /Domain
    - Entity, ValueObject, IRepository arayüzleri
  /Application
    - UseCase, Command, Handler, Servis interface’leri
  /Infrastructure
    - ORM, servis implementasyonları
  /WebUI
    - Controller, ViewModel, DTO, Mapping
  /Tests
    - Unit testler (Mock, Fake, Stub ile)

8. Avantajlar — Genişletilmiş

  • Uzun vadede kod sağlığı ve teknik borç azalır.
  • Takım büyüdükçe kod kalitesi ve iletişim bozulmaz.
  • Yeni gereksinimlerde hızlı ve risksiz değişiklik.
  • Dış sistem/teknoloji bağımlılığı en aza iner (ör. ORM değiştirmek çok kolay).
  • Domain bilgisiyle iş mantığı ön planda kalır, teknik detaylar merkeze bulaşmaz.

9. Dezavantajlar — Genişletilmiş

  • Başlangıçta öğrenme eğrisi yüksektir.
  • Daha fazla kod ve dosya üretimi, karmaşıklık (özellikle küçük ekiplerde).
  • Her geliştirici için tüm katmanların ayrımının net anlaşılması gerekir.
  • Küçük projelerde gereksiz yapı şişkinliği (overengineering).
  • Fazla interface, mapping ve ekstra test yönetimi yükü.

10. Gerçek Hayatta Karşılaşılan Mimari Tuzağı & Sık Yapılan Hatalar

  • Domain’de ORM referans etmek (entity framework gibi).
  • Controller’da iş mantığı yazmak, use case’i atlamak.
  • Tüm kodu use case’lere gömmek, SRP ihlali.
  • Bağımlılıkların IoC yerine “new” anahtar kelimesiyle elle yaratılması.
  • DTO ve Entity kavramını karıştırmak, controller’dan direkt entity döndürmek.
  • Repository veya servis interface’lerine gereğinden fazla sorumluluk eklemek (ISP ihlali).

11. Katmanlar Arası Karşılaştırmalı Tablo

Katman Görev SOLID Etkisi Bağımlılık Yönü
Entities (Domain) İş kuralları, entity/value obj. SRP, OCP, DIP Bağımsız
Use Cases (Application) Akış, senaryo, iş mantığı akışı SRP, DIP, ISP Domain, interface
Interface Adapters Mapping, DTO, UI bağlama ISP, OCP Application, domain
Frameworks & Drivers ORM, UI, dış servis, veri DIP, LSP Sadece adapters

12. Clean Architecture, SOLID ve Diğer Modern Mimari Yaklaşımlar

  • Onion Architecture: Temelinde benzer; Clean Architecture daha genel, Onion ise DDD ve dependency inversion’ı vurgular.
  • Hexagonal Architecture (Ports & Adapters): Giriş (ports) ve çıkış (adapters) soyutlamaları ile dış dünyayı izole eder.
  • Layered/N-Tier: Dikey katman ayrımı; genellikle domain merkeze konmaz, bağımlılıklar yukarıdan aşağıya.

13. Sıkça Sorulan Sorular (SSS) — Genişletilmiş

Her projenin Clean Architecture’a ihtiyacı var mı?
Karmaşık, uzun ömürlü, değişken iş mantığı gerektiren projeler için önerilir; basit CRUD uygulamalarında gereksiz olabilir.

SOLID’in en çok hangi ilkesi gerçek hayatta ihlal ediliyor?
SRP (tek sorumluluk) ve DIP (bağımlılık ters çevirmesi) en sık gözden kaçan ilkelerdir.

Altyapı teknolojisini değiştirirken domain etkilenir mi?
Hayır; Clean Architecture ile domain ve use case katmanları altyapıdan tamamen izole olur.

Tüm kodu tek bir yerde toplamak neden sorun?
Bakım, test, paralel geliştirme ve kodun tekrar kullanılabilirliği azalır.

Controller veya Service Layer’a iş mantığı yazmak yanlış mı?
Evet; iş mantığı domain/use case’de olmalı, controller sadece yönlendirmelidir.

14. Kaynaklar

By tanju.bozok

Software Architect, Developer, and Entrepreneur

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir