Giriş

Modern yazılımda kodun sürdürülebilirliği, bağımlılıkların yönetimi ve değişikliklere hızlı uyum için klasik N-Tier yaklaşımları artık yeterli gelmeyebiliyor. Onion Architecture (Soğan Mimari), özellikle Domain-Driven Design ve SOLID prensiplerini benimseyen projelerde, bağımlılıkların ters çevrilmesiyle (dependency inversion) çok daha test edilebilir ve esnek bir yapı sağlar.

onion-arc-giris

Bu rehberde, Onion Architecture’ın felsefesinden başlayıp, her katmanını detaylı, örneklerle ve pratik önerilerle açıklayacağız. Yazının sonunda, kendi projen için neden bu mimariyi tercih etmen (veya etmemen) gerektiğini net şekilde göreceksin.

1. Onion Architecture Nedir?

Tanım

Onion Architecture, geleneksel çok katmanlı (N-Tier) mimarinin eksiklerini kapatmak için tasarlanmış, bağımlılıkların merkeze (domaine) doğru yöneldiği ve dış bağımlılıkların izole edildiği bir mimari modeldir.

onion-arc-genel

Merkezde iş mantığı (domain), dış katmanlara doğru ise uygulama, servisler ve altyapı yer alır. Her katman bir öncekinin “kullandığı” değil, bağımlı olduğu yerdir. Bağımlılıklar merkeze doğru ters çevrilir (Dependency Inversion Principle).

2. Onion Architecture Katmanları

2.1 Domain (Core) Katmanı

  • Merkezdeki katman ve mimarinin “kalbi”dir.
  • Sadece iş mantığı (domain nesneleri, entity, value object, domain servisleri) burada yer alır.
  • Hiçbir dış bağımlılığı yoktur (veritabanı, dosya sistemi, framework vs.).
  • Tüm sistemin “ne” yaptığı burada tanımlanır, “nasıl” yaptığı dıştadır.

Pratik C# Örneği:

public class Product
{
    public int Id { get; }
    public string Name { get; }
    public Money Price { get; }
    // İş kuralları methodlar burada
}
  • Domain Event, Aggregate Root, Value Object gibi DDD kavramları uygulanabilir.

2.2 Domain Services (Domain Servisleri)

  • İş kurallarının (business rules) entity’ler üzerinde tanımlanamayacağı durumlarda domain servisleri devreye girer.
  • Statik değil, interface olarak tanımlanır.

Örnek:

public interface IDiscountService
{
    Money ApplyDiscount(Product product, Customer customer);
}

2.3 Application Katmanı

  • Kullanıcı senaryolarını (use case), akışları ve domain’in nasıl kullanılacağını belirler.
  • Domain katmanına bağımlıdır, altyapı katmanlarına bağımlı değildir.
  • Burada service, command, handler, workflow vb. işlemler bulunur.

Örnek:

public class RegisterOrderCommand
{
    public int ProductId { get; set; }
    public int CustomerId { get; set; }
}

public class RegisterOrderHandler
{
    private readonly IOrderRepository _repo;
    private readonly IDiscountService _discountService;

    public RegisterOrderHandler(IOrderRepository repo, IDiscountService discountService)
    {
        _repo = repo;
        _discountService = discountService;
    }

    public void Handle(RegisterOrderCommand command)
    {
        // Use case akışı burada, domain metotları çağrılır
    }
}

2.4 Infrastructure (Altyapı) Katmanı

  • Dış bağımlılıklar bu katmandadır (veritabanı, dosya, mail, API, ORM, üçüncü parti servisler).
  • Application ve Domain katmanlarındaki arabirimleri (interface) uygular.
  • Infrastructure katmanı, domain/application katmanına bağımlı olabilir, tersi asla olmaz.

Örnek:

public class EfProductRepository : IProductRepository
{
    // Entity Framework ile veritabanı işlemleri burada yapılır
}

2.5 Presentation (UI/API) Katmanı

  • Kullanıcıdan gelen istekleri karşılayan katmandır (Web API, MVC Controller, Blazor, Desktop App).
  • Yalnızca Application katmanı ile haberleşir.
  • Veri validasyonu, DTO (Data Transfer Object) dönüşümleri burada yapılır.

Örnek:

[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
    private readonly RegisterOrderHandler _handler;
    // ...
}

2.6 Cross-Cutting Concerns

  • Logging, validation, caching, exception handling gibi katmanlar arası ortak ihtiyaçlar
  • Genellikle altyapı (infrastructure) veya özel ayrı katmanlar olarak yapılandırılır
  • Decorator/desing pattern’larla entegre edilebilir

3. Katmanlar Arası Bağımlılık ve Dependency Inversion

  • Tüm bağımlılıklar daima merkeze (domain’e) doğru ilerler.
  • Domain hiçbir zaman uygulama, altyapı ya da UI’dan haberdar olmaz.
  • Application, domain’i bilir ama altyapıdan (db, mail, dosya) haberdar değildir.
  • Infrastructure, application ve domain arabirimlerini implement eder.
  • Bağımlılıkların çoğu Dependency Injection (IoC) ile çözülür.

4. Katmanlar Arası İletişim Akışı ve Katı Sınırlar

  • Arabirimler (interface), domain veya application’da tanımlanır; altyapı bunları uygular.
  • UI ve infrastructure, doğrudan birbirini asla çağırmaz.
  • DTO, ViewModel, Entity gibi nesneler birbirine karıştırılmaz; mapping yapılır.
  • Test edilebilirlik ve refactoring kolaylığı burada sağlanır.

5. Onion Architecture ile N-Tier/Layered Architecture Farkı

Kriter N-Tier/Layered Onion Architecture
Bağımlılık Yönü UI → Business → Data Dıştan merkeze (Domain)
Domain’in yeri Genelde ortada Tam merkezde
Test Edilebilirlik Orta Yüksek
Bağımlılıkların Yönetimi Sınırlı Ters çevrilmiş (DIP)
Modülerlik Orta Yüksek
SOLID Uyumu Kısmen Tam uyumlu
Uygulama Senaryosu Büyük, klasik projeler Modern, DDD tabanlı

 

onion-arc-karsilastirma

6. Gerçek Kullanım Senaryoları

  • Kurumsal DDD Projeleri: Büyük organizasyonlarda domain karmaşasını azaltır, modülerliği ve test edilebilirliği artırır.
  • Mikroservis Mimarilerinde: Her servis kendi onion yapısını kurar; servisler arası bağımsızlık üst seviyeye çıkar.
  • Modern Web API ve SaaS Uygulamaları: Değişen müşteri ihtiyaçlarına hızla uyum, refactoring ve unit test kolaylığı sağlar.

7. C# ile Onion Architecture Katmanlarını Kodlamak

Domain Katmanı

namespace MyProject.Domain
{
    public class Customer
    {
        public int Id { get; }
        public string Name { get; }
        // Domain logic burada
    }

    public interface ICustomerRepository
    {
        Customer GetById(int id);
        void Add(Customer customer);
    }
}

Application Katmanı

namespace MyProject.Application
{
    public class CreateCustomerCommand
    {
        public string Name { get; set; }
    }

    public class CustomerCommandHandler
    {
        private readonly ICustomerRepository _repo;
        public CustomerCommandHandler(ICustomerRepository repo)
        {
            _repo = repo;
        }
        public void Handle(CreateCustomerCommand command)
        {
            var customer = new Customer(command.Name);
            _repo.Add(customer);
        }
    }
}

Infrastructure Katmanı

namespace MyProject.Infrastructure
{
    public class EfCustomerRepository : ICustomerRepository
    {
        // EF Core ile veri işlemleri burada yapılır
    }
}

UI/API Katmanı

namespace MyProject.API
{
    [ApiController]
    [Route("api/customers")]
    public class CustomersController : ControllerBase
    {
        private readonly CustomerCommandHandler _handler;
        // ...
    }
}

8. Avantajlar

  • Test Edilebilirlik: Merkezdeki domain bağımsız olduğu için kolayca birim testi yapılabilir.
  • Esneklik: Altyapı değişirse (ör. farklı veritabanı ya da mail servisi), domain/application etkilenmez.
  • Uzun Vadede Bakım: İş kurallarını değiştirmek altyapıya dokunmadan mümkün.
  • SOLID ve DDD Uyumu: Dependency Inversion, Single Responsibility gibi prensipler doğrudan uygulanır.

9. Dezavantajlar

  • Başlangıçta Karmaşıklık: Yeni başlayanlar için katmanların yerleşimi kafa karıştırabilir.
  • Daha Fazla Soyutlama ve Kod: Arabirim, mapping ve bağımlılık yönetimi fazla kod anlamına gelir.
  • Küçük Projelerde Overengineering Riski: Basit ihtiyaçlar için gereğinden fazla “yapı” oluşturmak mümkündür.

10. Katmanlar Arası Veri Aktarımı ve DTO

  • Domain ile Application arasında domain nesneleri (Entity, Value Object) kullanılır.
  • UI ve Application arasında ise DTO/ViewModel ile veri taşınır.
  • Automapper gibi mapping araçları işlevsel olabilir.
  • Katmanlar arası model taşımada asla doğrudan “entity” kullanılmaz.

11. Sık Yapılan Hatalar

  • Altyapı bağımlılıklarını merkeze taşımak (ör. Entity Framework referansı domain’de kullanmak)
  • Arabirimlerin yanlış yerde tanımlanması
  • Domain’e iş mantığı değil, veri taşıma kodları eklemek
  • Tüm kodu domain’e gömmek veya domain’i ihmal etmek
  • Katman sınırlarının ihlal edilmesi

13. Karşılaştırmalı Tablo: Onion Architecture vs. N-Tier/Layered

Özellik Onion Architecture N-Tier/Layered
Domain’in Yeri Merkez Orta/iş katmanı
Bağımlılık Yönü Dıştan içe Yukarıdan aşağıya
Test Edilebilirlik Yüksek Orta
SOLID Uyumu Tam Kısmi
Refactoring Kolay Zor
Modern Mimariye Uygunluk Yüksek Orta

 

onion-arc-karsilastirma

14. Sıkça Sorulan Sorular (SSS)

Onion Architecture’ı ne zaman tercih etmeliyim?
Büyük, uzun vadeli, domain odaklı ve yüksek test gerektiren projelerde.

Her projeye uygun mu?
Hayır. Basit CRUD projelerde karmaşık gelebilir.

Mikroserviste Onion Architecture kullanılır mı?
Evet. Hatta en çok mikroservislerde fayda sağlar.

Katmanlar arası geçişte performans kaybı olur mu?
Genellikle göz ardı edilebilir, büyük sistemlerde doğru uygulama ile sorun çıkmaz.

Domain katmanında ORM kullanılmalı mı?
Hayır. ORM altyapı katmanına ait olmalı, domain’de referans olmamalı.

15. 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