Hexagonal & Onion Architecture

İş mantığını dış bağımlılıklardan soyutlamak için hexagonal (ports & adapters) ve onion architecture katmanları, bağımlılık yönleri ve test edilebilirlik anlatılır.

Hexagonal & Onion Architecture

Hexagonal & Onion Architecture: İş Mantığını Dış Dünyadan İzole Etmek

Tek bir projede bile, iş mantığınız (domain) veritabanına (SQL), UI'a (React/Web), dosya sistemine, üçüncü parti API'lere (Stripe, MailChimp) bağlanırsa ne olur? Kodunuz çimentolaşır (cement). Bir teknoloji değişikliğinde (ör. MSSQL'den PostgreSQL'e geçiş), tüm katmanları değiştirmek zorunda kalırsınız. Unit test yazmak imkânsızlaşır çünkü her test veritabanına bağlanmak zorundadır.

Hexagonal (Ports & Adapters) ve Onion (Soğan) mimarileri, bu bağımlılık kaosunu tersine çevirir. Tüm dış dünya (veritabanı, UI, kuyruklar) iş mantığını çevreleyen (surrounding) araçlar haline gelir, iş mantığı ise merkezde kalır. Bu mimarilerin ortak sloganı: "İş mantığın, altyapı hakkında hiçbir şey bilmemeli."


1. Onion Architecture (Soğan Mimari - Palermo)

Onion Architecture, 4 ana katmandan oluşur. Bağımlılık yönü dıştan içe değil, içten dışa doğrudur (Dependency Inversion).

  • Katman 1: Domain Models (Çekirdek)

    • En içteki katman. Entity, Value Object, Aggregate Root, Domain Service gibi DDD yapı taşları buradadır.

    • Hiçbir dış bağımlılığı yoktur. System, System.Collections.Generic dışında bir referans almaz.

  • Katman 2: Application (Uygulama Katmanı)

    • Use Case'ler (Komutlar ve Sorgular), DTO'lar, Repository interfaceleri, Unit of Work arayüzleri buradadır.

    • Domain katmanına bağımlıdır. Ancak veritabanı veya dosya sistemi gibi altyapı detaylarından tamamen izoledir.

    • MediatR Handler'ları genelde bu katmanda bulunur.

  • Katman 3: Infrastructure (Altyapı Katmanı)

    • Veritabanı (Entity Framework DbContext), Loglama, Mesaj Kuyruğu, Harici API istemcileri (HttpClient) buradadır.

    • Application katmanında tanımlanan arayüzleri (interfaceleri) implemente eder.

  • Katman 4: Presentation / UI (Sunum Katmanı)

    • Kullanıcı arayüzü (Web API, MVC, Console, Mobile) bu katmandadır.

    • Application ve Infrastructure katmanlarına bağımlıdır (Dependency Injection ile).

Bağımlılık Kuralı: Infrastructure ve UI, Application'a; Application, Domain'e bağımlıdır. Tersine bir bağımlılık asla yoktur! Yani Domain, Application'ı; Application, Infrastructure'ı tanımaz.


2. Hexagonal Architecture (Ports & Adapters - Cockburn)

Hexagonal Architecture (Altıgen Mimari), Onion'ın aynı prensipleri uygulayan ancak farklı bir terminoloji kullanan versiyonudur.

  • Domain (Core): İş mantığı ve kuralları. (Onion'daki Domain + Application katmanlarına denk gelir).

  • Ports (Limanlar): Uygulamanın dış dünya ile iletişim kurmak için tanımladığı arayüzlerdir (interfaces).

    • Giriş Portu (Inbound Port): Uygulamanın nasıl çağrılacağını tanımlar. (Örn: IOrderService, ICommandHandler).

    • Çıkış Portu (Outbound Port): Uygulamanın dış dünyadan nasıl veri alacağını tanımlar. (Örn: IOrderRepository, IEmailSender).

  • Adapters (Adaptörler): Belirli bir teknolojiyi (SQL, HTTP, RabbitMQ) ilgili porta bağlayan somut implementasyonlardır.

    • Giriş Adaptörü: UI (Controller), Command Line, Test Projesi -> Uygulamayı çağırır.

    • Çıkış Adaptörü: Entity Framework Core (Repository), SmtpClient (Email), Azure Service Bus.

Ana Fark: Hexagonal, bağımlılıkları "Port" ve "Adapter" olarak ikiye ayırarak, sistemin farklı dış etkenlerle (örneğin test senaryoları, farklı veritabanları) kolayca değiştirilebilir olmasını vurgular.


3. Ortak Faydalar ve Süper Güçleri

Fayda Açıklama
Test Edilebilirlik (Unit Test) Domain/Application katmanı hiçbir dış bağımlılık içermez. Repository veya HttpClient interface'lerini mock'layarak (Moq, NSubstitute) iş mantığını hızlıca test edebilirsiniz.
Teknoloji Değişimi (Framework Agnostic) Veritabanını EF Core'dan Dapper'a, veya MSSQL'den PostgreSQL'e geçmek isterseniz, sadece Infrastructure katmanını değiştirirsiniz. Application katmanından tek bir satır kod etkilenmez.
Sorumluluk Ayrımı (Separation of Concerns) Her katmanın net bir sorumluluğu vardır. Domain kuralları Application tarafından organize edilir, Infrastructure ise sadece mekanik işleri (DB, HTTP) yapar.
İş Odaklı (Domain-Centric) Kodunuz artık veritabanı tablolarına göre değil, iş alanı kurallarına göre şekillenir. Bu da uzun vadeli bakımı kolaylaştırır.

4. .NET Core ile Örnek Proje Yapısı

text

/ src
   / Domain                          (Katman 1 - Hiç bağımlılık yok)
       / Entities
           Order.cs
           OrderItem.cs
       / ValueObjects
           Money.cs
       / Interfaces (Sadece Domain servisleri için)
           IDiscountCalculator.cs
   / Application                     (Katman 2 - Domain'e bağımlı)
       / Orders
           / Commands
               CreateOrderCommand.cs
               CreateOrderHandler.cs (MediatR)
           / Queries
               GetOrderQuery.cs
               GetOrderHandler.cs
           / DTOs
               OrderDto.cs
       / Contracts (Ports - Outbound)
           IOrderRepository.cs      (Application, bu interface'i tanımlar)
           IUnitOfWork.cs
       / Common
           IRequestHandler.cs
   / Infrastructure                  (Katman 3 - Application'a bağımlı)
       / Persistence
           AppDbContext.cs
           Repositories
               OrderRepository.cs   (IOrderRepository'i implemente eder)
           Configurations
               OrderConfiguration.cs
       / Messaging
           RabbitMqPublisher.cs
       / External
           StripePaymentGateway.cs
   / WebAPI                          (Katman 4 - Application + Infrastructure'a bağımlı)
       / Controllers
           OrdersController.cs
       / Middleware
           ExceptionHandlingMiddleware.cs
       Program.cs (DI Kayıtları)

DI (Dependency Injection) Kayıtları (Program.cs):

csharp

// Infrastructure katmanındaki servisleri kaydet
builder.Services.AddDbContext<AppDbContext>(options => ...);
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();

// Application katmanındaki MediatR Handler'ları bul ve kaydet
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(CreateOrderHandler).Assembly));

5. "Ne Nereye Koyulur?" - Klasik Kafa Karışıklıkları

  • Repository Interface'leri Nerede? Application katmanında. Domain katmanına koymak yanlıştır çünkü Repository, bir altyapı (infrastructure) konseptidir. Domain, bir repo'nun varlığından haberdar olmamalıdır.

  • Entity Framework Core (DbContext) Nerede? Infrastructure katmanında. Application katmanı IOrderRepository'yi bilir, ancak DbContext veya DbSet'i asla bilmez.

  • Automapper (Mapping) Nerede? Application katmanında (veya Infrastructure). Domain Entity'leri DTO'ya çevirmek bir uygulama (use case) işidir. Domain katmanı AutoMapper'ı tanımaz.

  • FluentValidation (Validasyon) Nerede? Application katmanında. MediatR Pipeline Behavior ile birlikte.


6. En Büyük 3 Tuzak

  1. "Her Şeyi Soyutla" Tuzağı: Her veritabanı tablosu için bir interface tanımlamak, kod şişkinliğine (over-engineering) yol açar. Yalnızca iş mantığınızda gerçekten ihtiyaç duyduğunuz repository'leri soyutlayın.

  2. "Domain'in İçine Altyapı Sızdırmak": Domain katmanına using Newtonsoft.Json; veya using System.ComponentModel.DataAnnotations; yazmak büyük hatadır. Domain, framework'lerden arındırılmış (POCO) olmalıdır.

  3. "Servis Katmanı ile Domain Katmanını Karıştırmak": Application katmanındaki servisler (use case), Domain Entity'leri çağırır. Domain Entity'ler kendi iş kurallarını uygular (zengin model). Anemic Domain (kansız model) yaparak bu mimariyi bozmayın.


7. Hexagonal/Onion vs Clean Architecture (Robert C. Martin)

Bu üç mimari (Hexagonal, Onion, Clean) aynı prensipleri paylaşır:

  • Bağımlılık Yönü (Dependency Inversion).

  • İş mantığının merkezde olması.

  • Test edilebilirlik.

  • Framework bağımsızlığı.

Clean Architecture daha çok "Use Case" odaklıdır ve katmanları dört daire olarak çizer (Entities, Use Cases, Interface Adapters, Frameworks). Onion ve Hexagonal ise daha çok "Port" ve "Adapter" terimleriyle bu yapıyı somutlaştırır. Pratikte bu farklar terminolojik düzeydedir; hepsi aynı amaca hizmet eder.

Sonuç:

Hexagonal ve Onion Architecture, özellikle uzun ömürlü, karmaşık iş mantığına sahip uygulamalar için biçilmiş kaftandır. Sizi, veritabanı veya UI değişikliklerinden korur. Unit test yazmayı kolaylaştırır ve ekiplerin farklı katmanlarda paralel çalışmasına olanak tanır.

Ancak bu mimarinin bir bedeli vardır: Öğrenme eğrisi yüksektir ve basit CRUD uygulamaları için gereğinden fazla (over-engineering) olabilir. Eğer 2-3 yıl yaşayacak ve bir ekibin geliştireceği bir projeye başlıyorsanız, bu mimari size uzun vadede inanılmaz bir esneklik kazandıracaktır.

Size Tavsiyem: Yeni bir projeye başlarken, Domain ve Application katmanlarını ayrı projeler (sınıf kütüphaneleri) olarak oluşturun. Veritabanını (Infrastructure) ve UI'ı (WebAPI) bunlara bağımlı hale getirin. Bağımlılık oklarının yönüne asla dikkat edin: Dıştakiler içtekileri bilir, içtekiler dıştakileri asla bilmez! Bu kurala uyduğunuz sürece, kodunuz ölümsüz olacaktır.

Tüm yazılar

İlgili Yazılar