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.Genericdışı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, ancakDbContextveyaDbSet'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
-
"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.
-
"Domain'in İçine Altyapı Sızdırmak": Domain katmanına
using Newtonsoft.Json;veyausing System.ComponentModel.DataAnnotations;yazmak büyük hatadır. Domain, framework'lerden arındırılmış (POCO) olmalıdır. -
"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.