Domain-Driven Design (DDD) Nedir?
Domain-Driven Design (DDD - Etki Alanı Güdümlü Tasarım) nedir? Bounded Context (Sınırlı Bağlam), Ortak Dil (Ubiquitous Language), Aggregate ve mikroservisler.
Yozel
12 dakika

İçindekiler (21)
Büyük ölçekli kurumsal yazılım projelerinin başarısızlığa uğramasının en temel nedeni teknik yetersizlikler değil, iş birimleri (alan uzmanları / domain experts) ile yazılım geliştirme ekipleri arasındaki iletişim uçurumudur. Bankacılık, e-ticaret, sigortacılık, tedarik zinciri veya sağlık gibi yüzlerce karmaşık iş kuralının birbiriyle çarpıştığı sistemlerde, yazılımcıların veritabanı tabloları ve teknik terimlerle düşünmesi, iş analistlerinin ise tamamen farklı bir ticari jargonda konuşması "çeviri kayıplarına" yol açar. Eric Evans'ın 2003 yılında yayımladığı çığır açıcı "Domain-Driven Design: Tackling Complexity in the Heart of Software" kitabıyla literatüre kazandırdığı Domain-Driven Design (DDD - Etki Alanı Güdümlü Tasarım), yazılımın kalbindeki bu iş karmaşıklığını çözmek için geliştirilmiş en güçlü yazılım tasarım felsefesidir.
Günümüzde Uygulama ve Yazılım dünyasında mikroservis sınırlarının nereden çizileceğine karar vermekten yüksek trafikli sistemlerde CQRS (Command Query Responsibility Segregation) kurgulamaya kadar her ileri seviye mimari karar DDD prensiplerine dayanır. Bu kapsamlı rehberde; Stratejik DDD (Ubiquitous Language, Bounded Context, Context Mapping, Anti-Corruption Layer) ve Taktiksel DDD (Entity, Value Object, Aggregate Root, Domain Events) kavramlarını, Event Storming çalıştaylarını ve gerçek dünya uygulama pratiklerini derinlemesine inceliyoruz.
1. Domain-Driven Design (DDD) Nedir? Neden İhtiyaç Duyulur?
Domain-Driven Design (DDD), yazılımın yapısını ve kod içindeki isimlendirmeleri (sınıf adları, metot imzaları, modül sınırları) doğrudan çözmeye çalıştığı gerçek dünya iş alanıyla (Domain) birebir eşleştirmeyi hedefleyen bir yazılım geliştirme yaklaşımıdır. Geleneksel yazılım geliştirmede önce veritabanı şeması (ER diyagramı) çizilir, tablolar arasındaki yabancı anahtarlar (Foreign Key) belirlenir ve tüm uygulama bu tabloların etrafına inşa edilir. Ancak bu yaklaşım, yazılımı cansız veri taşıyıcılarına (Anemic Domain Model) dönüştürür.
DDD ise soruyu tersinden sorar: "Bu işletme gerçek hayatta nasıl çalışıyor, hangi kurallarla değer üretiyor ve hangi olaylar iş akışını tetikliyor?" DDD'de yazılım kodu sadece çalışan bir makine değil, aynı zamanda şirketin iş kurallarını anlatan canlı bir belgedir. Bir finans uzmanı ile bir yazılım mimarı aynı ekrana baktığında, kodun içindeki PolicyUnderwritingService veya CreditLimitExceededDomainEvent ifadelerinin ne anlama geldiğini hiçbir sözlüğe ihtiyaç duymadan aynı şekilde anlayabilmelidir.
2. Alt Alanların Sınıflandırılması: Core, Supporting ve Generic Subdomains
Bir şirketin tüm yazılım sistemleri aynı derecede stratejik öneme sahip değildir. Mühendislik eforunu her yere eşit dağıtmak, en değerli iş kurallarının ihmal edilmesine yol açar. DDD, büyük bir iş alanını (Domain) üç farklı alt alana (Subdomain) ayırarak kaynak optimizasyonu yapar:
- Core Domain (Çekirdek Alt Alan): Şirketin rakiplerine karşı asıl rekabet avantajını (competitive advantage) yarattığı, para kazandığı ve onu piyasada benzersiz kılan kalbidir. Örneğin bir hızlı teslimat (q-commerce) şirketi için "Dinamik Kurye Rotalama ve Varış Süresi Tahmin Algoritması" Core Domain'dir. Bu alan asla hazır paket yazılımla alınamaz; şirketin en kıdemli mühendisleri tarafından DDD prensipleriyle içeride (in-house) geliştirilmelidir.
- Supporting Subdomain (Destekleyici Alt Alan): Çekirdek işin çalışması için zorunlu olan ancak şirkete doğrudan rakiplerinden farklı bir üstünlük sağlamayan özel alanlardır. Örneğin aynı teslimat şirketindeki "Kurye Vardiya ve Hakediş Hesaplama Modülü" bir Supporting Subdomain'dir. Basit ve anlaşılır mimarilerle geliştirilebilir veya No-Code / Low-Code platformları ile hızlıca devreye alınabilir.
- Generic Subdomain (Genel / Standart Alt Alan): Her şirketin ihtiyaç duyduğu, çözülmüş ve standartlaşmış problemlerdir: Kimlik doğrulama (Authentication / SSO), e-posta/SMS gönderimi, muhasebe defteri veya standart ödeme geçidi entegrasyonları. Bu alanları sıfırdan yazmak mühendislik israfıdır; hazır SaaS çözümleri (Auth0, Keycloak, Stripe) veya açık kaynak kütüphaneler kullanılmalıdır.
3. Stratejik DDD (Strategic Design): Büyük Resmi ve Sınırları Çizmek
DDD denildiğinde birçok yazılımcının aklına hemen kod içindeki Entity ve Repository sınıfları gelse de, DDD'nin asıl gücü Stratejik Tasarım (Strategic Design) aşamasında yatar. Stratejik tasarım yapılmadan uygulanan taktiksel desenler başarısızlığa mahkumdur.
3.1. Ubiquitous Language (Ortak Dil / Her Yerde Geçerli Dil)
İş birimleri (ürün yöneticileri, finans uzmanları, operasyon liderleri) ile yazılımcıların toplantılarda, analiz dokümanlarında, Jira görevlerinde ve doğrudan kaynak kodun içinde kullandıkları ortak ve tekil dildir. Eğer iş birimi "Poliçe Zeyilnamesi Onaylandı" diyorsa, yazılımcı bunu kodda updateRecordStatus(id, 2) olarak yazamaz; kodda da policy.approveEndorsement() metodu bulunmalıdır. Ortak dildeki herhangi bir değişiklik doğrudan kodda yeniden yapılandırma (refactoring) anlamına gelir.
3.2. Bounded Context (Sınırlı Bağlam)
Stratejik DDD'nin en devrimsel kavramıdır. Devasa bir kurumsal şirkette tek bir "Birleşik Model" (örneğin tüm departmanların ortak kullandığı 150 sütunlu tek bir Product veya Customer sınıfı) yaratmaya çalışmak imkansızdır ve kaos üretir. Çünkü "Ürün (Product)" kelimesi şirketin farklı departmanları için bambaşka anlamlara gelir:
- Katalog ve Pazarlama Bağlamında (Catalog Context): Ürün; başlığı, yüksek çözünürlüklü görselleri, SEO açıklaması, yıldız puanları ve kategori ağacı olan bir vitrin nesnesidir.
- Depo ve Lojistik Bağlamında (Inventory / Shipping Context): Ürün; ağırlığı (gram), hacmi (desi), kırılabilirlik durumu, barkod numarası ve depodaki raf koridoru (A-12-4) olan fiziksel bir koliden ibarettir. Ürün açıklamasının veya yorumlarının lojistik için hiçbir önemi yoktur.
- Muhasebe ve Faturalama Bağlamında (Billing Context): Ürün; KDV oranı, GTİP kodu, birim maliyeti ve iskonto matrahı olan finansal bir satırdır.
İşte Bounded Context (Sınırlı Bağlam), belirli bir domain modelinin ve Ortak Dilin (Ubiquitous Language) tutarlı ve geçerli olduğu mantıksal sınırdır. Her Bounded Context kendi içinde bağımsız bir Product sınıfına ve kendi veritabanı şemasına sahip olur. Modern Composable Commerce ve mikroservis mimarilerinde her bir mikroservisin sınırı doğrudan bir Bounded Context sınırına karşılık gelir.
3.3. Context Mapping (Bağlam Haritalama) ve Anti-Corruption Layer (ACL)
Farklı Bounded Context'ler birbirleriyle nasıl haberleşir? Bu ilişki haritasına Context Map denir. İki bağlam arasında Shared Kernel (Ortak Çekirdek), Customer-Supplier (Müşteri-Tedarikçi) veya Conformist gibi ilişki türleri bulunabilir. Ancak en kritik savunma mekanizması Anti-Corruption Layer (ACL - Bozulmayı Önleyici Katman) desenidir:
Temiz ve modern tasarladığınız yeni bir mikroservisin, 15 yıllık eski bir ERP veya ana bilgisayar (mainframe) sistemiyle konuşması gerektiğinde, eski sistemin karmaşık ve kirli veri yapılarını kendi Domain modelinizin içine sokarsanız kendi modeliniz de "zehirlenir" (corrupt olur). Bunun önüne geçmek için araya bir ACL (Çevirici Adaptör) koyarsınız; ACL dış sistemin karmaşık SOAP/XML verisini alır ve sizin iç dünyanızdaki temiz Domain nesnelerine tercüme eder. Özellikle yapay zeka ile tersine mühendislik ve legacy sistem modernizasyonu projelerinde Strangler Fig deseniyle birlikte ACL kullanımı hayati önem taşır.
4. Taktiksel DDD (Tactical Design): İş Kurallarını Koda Dökmek
Sınırlı bağlamlar (Bounded Contexts) çizildikten sonra, bir bağlamın içindeki zengin iş kurallarını nesne yönelimli ve fonksiyonel prensiplerle nasıl modelleyeceğimizi Taktiksel DDD yapı taşları belirler:
4.1. Entity (Varlık)
Zaman içinde özellikleri (attributes) değişse bile benzersiz kimliği (Unique Identity / ID) ile tanımlanan ve yaşam döngüsü takip edilen nesnelerdir. Örneğin bir e-ticaret sistemindeki Customer (Müşteri) veya Order (Sipariş) birer Entity'dir. Bir müşterinin adı, soyadı, telefon numarası ve adresi tamamen değişse bile customerId = "USR-9081" olduğu sürece o nesne aynı müşteridir. İki Entity'nin eşitliği içindeki alanlara göre değil, ID'lerine göre kontrol edilir.
4.2. Value Object (Değer Nesnesi)
Kimliği (ID'si) olmayan, yalnızca barındırdığı niteliklerin değerleriyle tanımlanan ve değiştirilemez (Immutable) olan nesnelerdir. Örneğin Money(150.00, "TRY"), EmailAddress("info@teknolojiofis.com"), Coordinate(41.0082, 28.9784) veya DateRange(start, end) birer Value Object'tir. Cebinizdeki 100 TL'lik banknotun seri numarasının (kimliğinin) alışveriş açısından hiçbir önemi yoktur; başka bir 100 TL ile birebir aynı değeri taşır. Value Object'ler oluşturulurken kendi doğrulama kurallarını (self-validation) çalıştırır: Geçersiz bir e-posta formatıyla veya negatif para birimiyle bir Value Object bellekte asla oluşturulamaz. Bu yaklaşım, kodda ilkel veri tipleri (string, int) kullanma hastalığını (Primitive Obsession) ortadan kaldırır.
4.3. Aggregate ve Aggregate Root (Küme ve Küme Kökü)
Taktiksel DDD'nin en kritik işlem sınırı (transactional boundary) kavramıdır. Bir Aggregate, iş kuralları gereği her zaman birlikte tutarlı kalması gereken ilişkili Entity ve Value Object'lerin oluşturduğu bir kümedir. Her kümenin dış dünyaya açılan tek bir kapısı vardır ve buna Aggregate Root (Küme Kökü) denir.
Örneğin Order (Sipariş) bir Aggregate Root'tur; içindeki OrderLineItem (Sipariş Kalemleri) ve ShippingAddress (Teslimat Adresi) ise bu kümenin alt parçalarıdır. Dışarıdaki hiçbir sınıf doğrudan orderLineItem.setQuantity(5) diyerek sipariş kalemini değiştiremez! Çünkü siparişte "Toplam sipariş tutarı müşterinin kurumsal kredi limitini aşamaz" veya "Kargoya verilmiş bir siparişin kalemleri değiştirilemez" gibi değişmez iş kuralları (Invariants) vardır. Dış dünya yalnızca Aggregate Root olan order.changeItemQuantity(productId, 5) metodunu çağırabilir; Order sınıfı gerekli tüm iş kurallarını denetler, toplam tutarı günceller ve veritabanına tek bir atomik işlem (ACID transaction) olarak kaydedilir.
4.4. Domain Events (Etki Alanı Olayları)
İş alanında geçmişte gerçekleşmiş ve iş birimleri için önem taşıyan olayları temsil eden değiştirilemez (immutable) kayıtlarıdır. İsimlendirmeleri her zaman geçmiş zaman kipindedir: OrderPlaced (Sipariş Verildi), PaymentFailed (Ödeme Başarısız Oldu), InventoryDepleted (Stok Tükendi). Domain Events sayesinde farklı Aggregate'ler ve farklı Bounded Context'ler birbirine sıkı sıkıya bağlanmadan (loose coupling) haberleşir. Sipariş onaylandığında Order kümesi sadece OrderConfirmedEvent yayınlar; bu olayı dinleyen Fatura Servisi faturayı keser, Bildirim Servisi SMS gönderir ve Puan Servisi müşteriye sadakat puanı yükler. Bu yapı, aynı zamanda merkezsiz web ve dağıtık blokzincir mimarilerindeki akıllı kontrat olay (event log) mekanizmalarıyla da birebir aynı felsefeyi paylaşır.
4.5. Domain Services, Repositories ve Factories
- Domain Service: Doğası gereği tek bir Entity veya Value Object'in içine sığmayan, birden fazla küme arasında koordinasyon veya hesaplama yapan durumsuz (stateless) iş mantığı sınıflarıdır (Örn: Döviz kuru üzerinden iki farklı hesap arasında çapraz transfer hesaplayan
CurrencyExchangeDomainService). - Repository: Yalnızca ve yalnızca Aggregate Root nesnelerini veritabanından belleğe yüklemek ve kaydetmek için kullanılan, koleksiyon benzeri soyut arayüzlerdir. Alt varlıklar (örneğin
OrderLineItem) için ayrı bir Repository asla oluşturulmaz! - Factory: Oluşturulması çok karmaşık olan Aggregate'lerin tüm iş kuralları doğrulanmış şekilde bellekte ilk kez inşa edilmesini kapsülleyen yapılardır.
5. Anemic Domain Model (Kansız Model) vs. Rich Domain Model (Zengin Model)
Martin Fowler'ın bir "anti-pattern" olarak tanımladığı Anemic Domain Model (Kansız / Cılız Model), nesne yönelimli programlama yaptığını zanneden ancak aslında prosedürel kod yazan ekiplerin en yaygın hatasıdır. Kansız modelde Order sınıfının içinde yalnızca değişkenler ve get/set metotları bulunur; hiçbir davranış veya iş kuralı yoktur. Tüm iş kuralları OrderService veya OrderManager adındaki binlerce satırlık devasa servis sınıflarının içine yığılmıştır. Bu durum Kapsülleme (Encapsulation) ilkesini tamamen yok eder.
DDD ise Rich Domain Model (Zengin Model) yaklaşımını şart koşar: Veri ve o veriyi değiştiren iş kuralları aynı sınıfın içinde yaşar. order.setStatus("CANCELLED") gibi dışarıdan açık bir setter metodu bulunmaz; bunun yerine order.cancel(reason) metodu bulunur ve bu metot siparişin iptal edilebilir durumda olup olmadığını bizzat kendi içinde denetler.
| Karşılaştırma Boyutu | Anemic Domain Model (Kansız Model) | Rich Domain Model (DDD Zengin Model) |
|---|---|---|
| Veri ve Davranış İlişkisi | Veri Entity'de, iş kuralları Service sınıflarında ayrıdır | Veri ve iş kuralları Entity / Aggregate içinde bir aradadır |
| Kapsülleme (Encapsulation) | Zayıf (Her alan için açık public setter bulunur) |
Güçlü (Durum yalnızca iş metotlarıyla kontrollü değişir) |
| Kod Tekrarı Riski | Yüksek (Farklı servisler aynı doğrulama kuralını tekrar yazar) | Sıfıra Yakın (Kural Aggregate Root içinde tek merkezdedir) |
| Birim Test Edilebilirlik | Zor (Servis bağımlılıklarını mock'lamak gerekir) | Çok Kolay (Saf Domain nesnesi sıfır bağımlılıkla test edilir) |
| Uygun Olduğu Projeler | Basit CRUD ve veri taşıma uygulamaları | Karmaşık iş kurallarına sahip kurumsal Core Domain sistemleri |
6. Event Storming: DDD'yi Keşfetmenin En Etkili Atölye Tekniği
Alberto Brandolini tarafından geliştirilen Event Storming, DDD projelerinde iş alanını (Domain) analiz etmek ve Bounded Context sınırlarını keşfetmek için kullanılan hızlı, görsel ve işbirlikçi bir atölye (workshop) tekniğidir. Aylar süren sıkıcı analiz dokümanları yerine; iş uzmanları (Domain Experts), ürün sahipleri ve yazılımcılar devasa bir duvarın veya dijital beyaz tahtanın (Miro/FigJam) önünde buluşur ve renkli yapışkan kağıtlarla (post-it) sistemin tamamını birkaç saat içinde modeller:
- Turuncu Kağıtlar (Domain Events): Sistemde gerçekleşen tüm iş olayları geçmiş zaman kipinde kronolojik sırayla duvara yapıştırılır (Sipariş Oluşturuldu, Ödeme Alındı, Fatura Kesildi).
- Mavi Kağıtlar (Commands): Bu olayları tetikleyen kullanıcı aksiyonları veya sistem komutları olayların soluna eklenir (Siparişi Tamamla, Ödeme Yap).
- Sarı Kağıtlar (Aggregates): Komutu kabul edip iş kurallarını çalıştırarak olayı üreten küme kökleri belirlenir.
- Mor / Pembe Kağıtlar (Policies & Hotspots): "X olayı olduğunda her zaman Y komutunu çalıştır" şeklindeki reaktif iş politikaları ve iş birimlerinin üzerinde anlaşamadığı darboğazlar (Hotspots) işaretlenir.
Çalıştayın sonunda doğal olarak gruplanan kümeler ve terminolojinin değiştiği kırılma noktaları, sistemin Bounded Context (ve dolayısıyla mikroservis) sınırlarını kusursuz bir doğrulukla ortaya çıkarır.
7. DDD ve CQRS (Command Query Responsibility Segregation) Sinerjisi
Karmaşık bir DDD modelinde Aggregate'ler iş kurallarını korumak ve yazma (Write) işlemlerinde tam tutarlılık sağlamak üzere tasarlanmıştır. Ancak ekranda 10 farklı tablodan veri birleştirerek karmaşık bir rapor veya yönetim paneli listesi göstermek istediğinizde (Read işlemi), bu veriyi Aggregate Root üzerinden çekmeye çalışmak hem çok yavaştır hem de modelin yapısını bozar. İşte bu noktada Greg Young tarafından popülerleştirilen CQRS (Komut ve Sorgu Sorumluluğunun Ayrılması) devreye girer:
- Command (Yazma) Tarafı: Veriyi değiştiren işlemler (Create, Update, Delete) zengin DDD modeli (Aggregate Root ve Repository) üzerinden yürütülür ve tüm iş kuralları sıkı biçimde denetlenir.
- Query (Okuma) Tarafı: Veriyi değiştirmeyen listeleme ve raporlama sorguları DDD katmanını tamamen bypass eder; doğrudan optimize edilmiş SQL sorguları (Dapper, Raw SQL) veya okuma için özelleştirilmiş NoSQL/Elasticsearch görünümleri (Materialized Views) üzerinden doğrudan DTO olarak ekrana döner.
Sıkça Sorulan Sorular (SSS)
1. Domain-Driven Design (DDD) bir yazılım mimarisi midir?
Tam olarak değil; DDD bir yazılım tasarım yaklaşımı ve metodolojisidir. Tek başına bir katmanlı mimari dikte etmez ancak pratikte neredeyse her zaman Clean Architecture, Hexagonal Architecture (Ports & Adapters) veya Onion Architecture ile birlikte uygulanır; çünkü DDD'nin zengin Domain katmanını korumak için dış dünya bağımlılıklarının tersine çevrilmesi şarttır.
2. Her projede DDD kullanmalı mıyız?
Kesinlikle hayır. Eğer projenizde karmaşık iş kuralları yoksa ve yapılan iş çoğunlukla veritabanına form kaydetmek ve listelemekten (CRUD) ibaretse, Taktiksel DDD (Aggregate, Value Object, Domain Event) uygulamak ciddi bir aşırı mühendislik (over-engineering) maliyetidir. Ancak Stratejik DDD kavramları (Ortak Dil ve Bounded Context) her ölçekteki projede faydalıdır.
3. Bir Aggregate içinde başka bir Aggregate nesnesini doğrudan referans tutabilir miyim?
DDD'nin altın kurallarından birine göre bir Aggregate, başka bir Aggregate Root'u doğrudan nesne referansı olarak içinde barındırmamalıdır; yalnızca onun ID'sini (Value Object olarak) referans tutmalıdır. Örneğin, Order kümesi içinde tüm Customer nesnesi değil, yalnızca CustomerId tutulur. Bu kural bellek şişmesini önler ve işlem (transaction) sınırlarını korur.
4. Mikroservis mimarisine geçmek için DDD bilmek zorunlu mudur?
Büyük ölçekli sistemlerde evet. Mikroservis projelerinin en büyük felaketi olan "Dağıtık Monolit" (Distributed Monolith - birbirine senkron HTTP çağrılarıyla göbekten bağlı servisler) tuzağına düşmemenin tek yolu, servis sınırlarını teknik tablolara göre değil DDD'nin Bounded Context sınırlarına göre ayırmaktır.
5. Value Object ile Entity arasındaki farkı en hızlı nasıl anlarım?
Kendinize şu soruyu sorun: "İki nesnenin içindeki tüm özellikler (alanlar) birebir aynı olduğunda, bunların iki ayrı nesne olmasının iş açısından bir önemi var mı?" Eğer önemi yoksa (örneğin iki adet "100 TL" veya iki adet aynı "Teslimat Adresi") bu bir Value Object'tir. Eğer tüm özellikleri aynı olsa bile kimlikleri farklı olduğunda farklı varlıkları temsil ediyorlarsa (örneğin aynı ad ve soyada sahip iki farklı vatandaş/müşteri) bu bir Entity'dir.
Yozel
Codex haberleri en az iki bağımsız kaynakla doğrulanır; düzeltmeler yazının altında tarih ve saatle belirtilir. Yayın ilkelerimiz