Onion Architecture: Bağımlılıkların Tersine Çevrilmesi ile Sağlam ve Test Edilebilir Sistem Tasarımı
1. GİRİŞ
Onion Architecture (Soğan Mimarisi), uygulama çekirdeğini (domain) dış kaynaklardan izole ederek değişime dayanıklı, test edilebilir ve sürdürülebilir sistemler inşa etmeye odaklanan bir mimari yaklaşımdır. Jeffrey Palermo tarafından popüler hale getirilen bu model, bağımlılıkların yönünü dikkatli yöneterek altyapı, UI ve framework değişimleri karşısında iş kurallarının sabit kalmasını sağlar. Modern bulut‑native ve mikroservis uygulamalarında, Onion Architecture uzun vadeli bakım maliyetlerini azaltmak ve ekiplerin bağımsız çalışmasını sağlamak için sıkça tercih edilir.
Bu konu neden bugün önemli?
- Hızla değişen teknoloji yığını (DB, messaging, UI framework) uygulama çekirdeğini bozmadan güncellenebilmelidir.
- Test otomasyonu ve CI/CD süreçleri işleyişi iyileştirir; domain izolasyonu birim testleri basitleştirir.
- Takımların ölçeklenmesiyle birlikte sorumluluk sınırlarının netleştirilmesi gereği artar; Onion Architecture bu konuda rehberlik eder.
Kimler için önemli?
- Yazılım mimarları ve teknik liderler
- Backend geliştiriciler, platform mühendisleri ve SRE ekipleri
- Uzun ömürlü, karmaşık domain'ler (finans, telekom, e‑ticaret) üzerinde çalışan takımlar
Hangi problemleri çözüyor?
- Domain kodunun UI, veritabanı veya framework değişiklikleriyle kirlenmesini engeller.
- Bir değişikliğin sistemin her yerine yayılmasını önleyerek refactor maliyetini düşürür.
- Testing, mocking ve component izolasyonunu kolaylaştırır.
2. KAVRAMSAL TEMELLER
2.1 Onion Architecture nedir — kısa tanım
Onion Architecture, uygulamayı içten dışa doğru tanımlanan katmanlara bölen bir mimaridir. En içte domain (entity, value object, domain servisleri) bulunur; dış katmanlar ise uygulama servisleri, arayüz adaptörleri ve altyapı implementasyonlarıdır. Temel fikir: iç katmanlar dış katmanlara bağımlı olmamalıdır; dış katmanlar iç katmanlarda tanımlanmış soyutlama (interface/port) üzerinden davranır.
2.2 Temel bileşenler ve terminoloji
- Domain / Core: Entity'ler, value object'ler, domain servisleri ve business rules. Bağımsız, framework‑free kod.
- Application Layer: Use case'ler, orchestrator'lar ve uygulama spesifik iş akışları; domain'i kullanarak operasyonları gerçekleştirir.
- Interface / Adapter Layer: UI controller'ları, DTO mapping, presenter'lar ve çeviri görevleri.
- Infrastructure Layer: Veritabanı, message bus, cache, e‑mail, dış API implementasyonları.
- Dependency Rule: İç katmanlar dış katmanlara bağımlı olamaz; bağımlılık içe doğru olmalıdır.
2.3 Onion Architecture vs Clean / Hexagonal / Ports & Adapters
Bu mimariler arasında önemli bir farka göre değil, terminoloji ve şematik temsil açısından fark vardır. Hepsinin ortak noktası domain’in merkezde olması ve bağımlılıkların yönetilmesidir. Onion, Clean ve Hexagonal yaklaşımları pratikte benzer hedeflere yönelir; seçim genelde ekip alışkanlıkları ve terminoloji tercihleriyle ilgilidir.
3. NASIL ÇALIŞIR? — TEKNİK MİMARİ, AKIŞ VE UYGULAMA
3.1 Sistem mimarisi — katmanların rolü
Onion mimarisinde tipik katmanlar içten dışa şu şekildedir:
- Domain/Core: İş kuralları ve model. Hiçbir dış bağımlılık içermez.
- Application: Use case'ler, DTO'lar, application servisleri. Domain ile konuşur ve dış ara katmanlardan bağımsız kalır.
- Interfaces / Adapters: API controller'ları, view modelleri, mapping kodları.
- Infrastructure: Veritabanı implementasyonları, dış servis adaptörleri, IoC container konfigürasyonu.
3.2 Bağımlılık kuralı ve interface'lerin rolü
İç katmanlar (domain/application) dış katmanlara dair interface'ler (ör. IRepository, IEmailSender) tanımlar. Concrete implementasyonlar (SQLRepository, SmtpEmailSender) dış katmanda yer alır ve uygulama başında (composition root) domain tarafından tanımlanan interface'lere enjekte edilir. Bu strateji dependency inversion principle (DIP) ile uyumludur ve domain'in altyapıdan bağımsız kalmasını sağlar.
3.3 Use case ve transaction yönetimi
Use case'ler application layer'da bulunur ve genelde bir transaction boundary kurar. Bu katman, domain nesneleri üzerinde işlemleri koordine eder ve sonuçları commit/rollback mantığıyla yönetir. Transactional outbox gibi pattern'lar burada uygulanarak event publish atomik hale getirilebilir.
3.4 Veri akışı — örnek senaryo
- HTTP POST ile gelen bir istek Controller tarafından alınır (adapter katman).
- Controller isteği validate eder, DTO'ya dönüştürür ve ilgili Use Case'e iletir.
- Use Case domain service ve entity'leri kullanarak iş kurallarını ve state değişimini uygular.
- Use Case repository interface'ini çağırır; repository implementasyonu infrastructure katmandadır ve DB'ye yazma işlemini yapar.
- Use Case sonucu Controller'a döner ve uygun HTTP yanıtı üretilir.
3.5 Mapping, DTO ve anti‑corruption
Adapter katmanında veri dönüşümleri (DTO ↔ domain) yapılır. Bu dönüşümler intention revealing (niyeti gösterir) olmalı ve domain modelini kirletmemelidir. Dış sistemlerden gelen karmaşık ve farklı semantik veriler için anti‑corruption layer uygulayarak domain'e temiz ve anlamlı veriler sağlayın.
3.6 Test stratejileri
- Unit tests: Domain ve Use Case'leri dış bağımlılıklardan izole ederek test edin; repository/adapter interface'lerini mock'layın.
- Integration tests: Repository implementasyonlarını veri tabanı ile entegre testler; test container veya in‑memory DB kullanımı önerilir.
- End‑to‑end tests: Tam akışı (HTTP → controller → use case → DB) doğrulayın; staging ortamında gerçek altyapı ile test edin.
4. GERÇEK DÜNYA KULLANIMLARI
4.1 E‑ticaret: Sipariş ve envanter yönetimi
Onion Architecture e‑ticaret domain'inde sık kullanılır. Domain (order, inventory, pricing kuralları) altyapıdan bağımsız tutulur. Örneğin ödeme sağlayıcısını değiştirmek veya veritabanını ölçeklendirmek domain kodunu etkilemez; yalnızca infrastructure implementasyonları güncellenir.
4.2 Finans ve ödeme sistemleri
Finansal uygulamalar için auditability ve domain doğruluğu kritiktir. Onion mimarisi, iş kurallarını merkezde tutarak regülasyonlar ve audit talepleri değiştiğinde bile core'un sabit kalmasını sağlar. Transactional outbox ve event sourcing pattern'leri ile birlikte kullanılabilir.
4.3 Enterprise uygulamalar ve mikroservis dönüşümleri
Büyük kurumsal kod tabanlarının modularizasyonunda Onion Architecture yardımcı olur. Monolitik bir uygulamadan microservice'e geçerken domain boundary'lerini netleştirmek ve bağımlılıkları yönetmek için kullanılır; Strangler Fig pattern ile adım adım refactor yapılabilir.
5. AVANTAJLAR VE SINIRLAMALAR
Avantajlar
- Domain izolasyonu sayesinde değişiklik maliyeti azalır ve refactor güvenli hale gelir.
- Test edilebilirlik artar; birim testler ve mocking kolaylaşır.
- Bağımlılıklar kontrol altına alınır; altyapı veya UI değişiklikleri domain'i etkilemez.
- Takımlar için net ownership ve sorumluluk sınırları sağlar.
Sınırlamalar
- Başlangıç maliyeti: küçük projelerde fazla soyutlama zaman ve çalışma maliyeti getirebilir.
- Fazla interface ve mapping kodu üretilebilir; bu da karmaşıklığı artırabilir.
- Yanlış uygulama (interface sızıntıları, dış katmanda domain logic yazılması) faydayı azaltır.
6. ALTERNATİFLER VE KARŞILAŞTIRMA
| Yaklaşım | Avantaj | Dezavantaj |
|---|---|---|
| Monolitik katmanlı (Layered) | Basit, hızlı başlangıç | Domain'in UI/DB ile karışma riski yüksek |
| Hexagonal / Ports & Adapters | Domain merkezli, port/adapters ile esneklik | Terminoloji farklılığı; pratikte benzer |
| Onion Architecture | Dependency rule ile net izolasyon; test kolaylığı | Ek soyutlama ve mapping kodu gerekebilir |
7. EN İYİ PRATİKLER
Production kullanımı
- Küçük adımlarla başlayın: önce domain modelini ve use case'leri izole edin, sonra adapter/infra'yı ekleyin.
- Composition root'u tek yerde tutun ve dependency wiring'i merkezi yönetin.
- Interface'leri domain tarafından tanımlayın; implementasyonları dış katmanda tutun.
- Transactional outbox, sagas veya event sourcing gibi pattern'ları ihtiyaç doğrultusunda kullanın; atomicity gereksinimini değerlendirin.
Performans optimizasyonu
- Mapping ve DTO dönüştürmelerini optimize edin; gereksiz kopyalamaları azaltın.
- Hot path'leri profil edin; domain katmanında IO olmamasına dikkat edin.
- Cache stratejilerini adapter seviyesinde uygulayın; domain tarafında cache mantığını saklamayın.
Güvenlik
- Authentication/authorization adapter seviyesinde uygulanmalı; domain sadece yetki bilgisini kullanarak karar vermelidir.
- Input validation iki katmanda yapılmalı: transport‑level (controller) ve domain‑level (invariant validation).
Observability
- Use case çağrı süreleri, adapter hataları ve DB latency gibi metrikleri toplayın.
- Correlation ID ile trace'leri uçtan uca korelasyonlayın; log seviyelerini katmana göre ayarlayın.
8. SIK YAPILAN HATALAR
- Domain yerine infrastructure interface'leri tanımlamak — bu dependency rule'ı ihlal eder.
- Adapter'larda domain mantığı bırakmak — iş kuralları core'da kalmalı.
- Composition root'un scattered olması — bağımlılık konfigürasyonlarını merkezi tutun.
- Fazla soyutlama: geliştirici hızını düşürebilir; pragmatic karar verin.
9. GELECEK TRENDLER
- Tooling ve kod jenerasyonu: Domain modellerinden otomatik interface ve DTO üretimi, mimari standardizasyonu hızlandıracak.
- AI destekli refaktoring: Büyük kod tabanlarında katman ayrıştırma ve dependency analizini otomatik öneren araçlar yaygınlaşacak.
- Serverless adaptasyon: Onion ilkeleri, function‑oriented ve serverless mimarilere uyarlanarak küçük, bağımsız use case'ler şeklinde uygulanacak.
EK BÖLÜMLER
Sık Sorulan Sorular (FAQ)
- 1. Onion Architecture her projeye uygulanmalı mı?
Hayır. Küçük veya kısa ömürlü projelerde fazla mühendislik olabilir. Ancak uzun vadeli bakım, test ve değişim ihtiyacı olan projeler için faydası yüksektir.
- 2. Domain interface'lerini nerede tanımlamalıyım?
Domain veya application katmanında tanımlayın. Interface'leri domain'in bir parçası olarak görmek, implementasyonların infrastructure tarafında kalmasını sağlar.
- 3. Mapping kodu nerede tutulmalı?
Mapping adapter katmanında veya dedicated mapping servislerinde toplanmalıdır; böylece dönüşümler merkezi ve test edilebilir olur.
- 4. Onion vs Hexagonal farkı nedir?
Pratikte çok benzerler; fark genelde terminoloji ve çizim tercihidir. Her iki yaklaşım da domain'i merkez alır ve bağımlılığın yönünü kontrol eder.
- 5. Composition root nedir ve neden önemlidir?
Composition root uygulama başında dependency'lerin bağlandığı tek noktadır. Bu merkezi yapı test ve konfigürasyon yönetimini kolaylaştırır.
- 6. Transactional outbox Onion içinde nerede yer alır?
Application (use case) katmanında transaction yönetimi yapılır; outbox pattern implementasyonu infrastructure tarafında yer alır fakat orchestration use case tarafından tetiklenir.
- 7. Monolitik projede nasıl başlarım?
Domain paketini izole ederek başlayın; sonra application ve adapter katmanlarını ayırın. Strangler Fig pattern ile adım adım taşıma uygulayın.
- 8. Hangi metrikleri izlemeliyim?
Use case latency, adapter error rate, DB query latency, integration error rate, unit/integration test coverage gibi metrikler önemlidir.
Anahtar Kavramlar
- Domain
- İş kuralları ve model; Onion'ın merkezinde yer alır.
- Application / Use Case
- Uygulama mantığı ve iş akışları; domain'i kullanır.
- Adapter
- Dış sistemlerle etkileşim sağlayan implementasyonlar.
- Infrastructure
- Veritabanı, messaging, dış API adaptörleri.
- Composition Root
- Bağımlılıkların bağlandığı tek başlangıç noktası.
Öğrenme Yol Haritası
- 0–1 ay: SOLID prensipleri, dependency inversion ve temel katmanlı mimari bilgilerini öğrenin; küçük bir domain modeli yazın.
- 1–3 ay: Use case izolasyonu, interface‑based repository ve adapter pattern'larını uygulayın; birim testler yazın.
- 3–6 ay: Composition root, DI container ve entegrasyon testleri ile pratik uygulamalar yapın; Strangler Fig ile legacy taşıma deneyin.
- 6–12 ay: Event sourcing, transactional outbox, CQRS entegrasyonu ve production‑grade observability üzerine deneyim kazanın.