Domain-Driven Design Nedir?
Domain-Driven Design (DDD), Eric Evans'ın 2003 yılında yayımladığı "Domain-Driven Design: Tackling Complexity in the Heart of Software" kitabıyla yazılım dünyasına tanıttığı bir mimari yaklaşımdır. DDD'nin temel felsefesi, yazılım geliştirme sürecinde iş alanının (domain) merkeze konulması ve teknik kararların iş gereksinimlerinden türetilmesidir.
Geleneksel yazılım geliştirmede, teknik altyapı genellikle projenin merkezinde yer alır ve iş mantığı bu altyapının kısıtlamalarına uyum sağlamak zorunda kalır. DDD bu yaklaşımı tersine çevirerek, iş alanının karmaşıklığını anlamayı ve bu karmaşıklığı yazılım modelinde doğru bir şekilde yansıtmayı hedefler.
Ubiquitous Language (Ortak Dil)
DDD'nin en temel kavramlarından biri Ubiquitous Language'dır. Bu kavram, geliştirme ekibi ve iş uzmanları arasında ortak bir dil oluşturulmasını ifade eder. Kod, belgeler, konuşmalar ve toplantılarda aynı terimlerin tutarlı olarak kullanılması gerekir.
Neden Önemli?
Yazılım projelerinin başarısızlık nedenlerinin büyük kısmı iletişim sorunlarından kaynaklanır. Geliştiriciler teknik jargon kullanırken, iş uzmanları kendi terminolojilerini kullanır. Bu kopukluk, yanlış anlama ve hatalı gereksinimlere yol açar. Ubiquitous Language bu sorunu ortadan kaldırır.
"İsim olan her şey tartışılmalıdır. Eğer bir kavramın ismi üzerinde uzlaşı yoksa, muhtemelen kavramın kendisi üzerinde de uzlaşı yoktur." - Eric Evans
Ubiquitous Language Nasıl Oluşturulur?
- İş uzmanlarıyla düzenli workshop'lar yapın
- Event Storming oturumları düzenleyin
- Terimleri bir sözlükte (glossary) belgeleyin
- Kod içindeki sınıf, metot ve değişken isimlerinde bu terimleri kullanın
- Teknik terimleri iş terimleriyle karıştırmaktan kaçının
Bounded Context (Sınırlı Bağlam)
Bounded Context, DDD'nin stratejik tasarım katmanındaki en kritik kavramdır. Büyük ve karmaşık bir domain'i daha küçük, yönetilebilir ve tutarlı alt alanlara bölmeyi sağlar. Her Bounded Context kendi Ubiquitous Language'ına, kendi modeline ve kendi kurallarına sahiptir.
Bounded Context Örnekleri
Bir e-ticaret platformunu düşünelim. "Müşteri" kavramı farklı bağlamlarda farklı anlamlar taşır:
| Bounded Context | "Müşteri" Anlamı | İlgili Özellikler |
|---|---|---|
| Satış | Sipariş veren kişi | Sepet, sipariş geçmişi, ödeme bilgileri |
| Müşteri Destek | Destek talep eden kişi | Ticket'lar, iletişim geçmişi, SLA |
| Pazarlama | Hedef kitle üyesi | Segmentasyon, kampanya geçmişi, tercihler |
| Faturalama | Ödeme yapan taraf | Fatura adresi, vergi numarası, ödeme yöntemi |
Her bağlamda "Müşteri" farklı özelliklere sahiptir ve farklı kurallara tabidir. Tüm bu kavramları tek bir "Customer" sınıfında toplamaya çalışmak, dev ve bakımı zor bir monolitik model oluşturur.
Context Mapping
Bounded Context'ler arası ilişkiler Context Map ile belirlenir. Temel ilişki kalıpları şunlardır:
- Shared Kernel: İki context'in paylaştığı ortak model
- Customer-Supplier: Bir context diğerine veri sağlar
- Conformist: Downstream context, upstream modeli olduğu gibi kabul eder
- Anti-Corruption Layer: Harici sistemlerden gelen verileri kendi modeline dönüştüren koruyucu katman
- Open Host Service: Diğer context'lerin kullanması için açık bir API
- Published Language: Context'ler arası iletişim için tanımlanmış standart format
Taktik Tasarım Yapı Taşları
Entity (Varlık)
Entity, kimliği ile tanımlanan domain nesnesidir. İki entity'nin aynı olup olmadığı, özelliklerinin değil kimliğinin (ID) karşılaştırılmasıyla belirlenir. Örneğin, bir "Sipariş" entity'sinin sipariş numarası onu benzersiz kılar. İçeriği zamanla değişse bile aynı sipariş olmaya devam eder.
Value Object (Değer Nesnesi)
Value Object, kimliği olmayan ve yalnızca değeriyle tanımlanan nesnedir. İki Value Object aynı değerlere sahipse eşittir. Immutable (değiştirilemez) olmaları en önemli özellikleridir. Para birimi, adres, tarih aralığı ve e-posta adresi Value Object örnekleridir.
Value Object'in Avantajları
- Immutable olmaları sayesinde thread-safe çalışırlar
- Yan etki (side effect) oluşturmazlar
- Test edilmeleri kolaydır
- Domain mantığını kapsüllerler (örneğin, bir Para value object'i farklı para birimleri arasında dönüşüm kurallarını içerebilir)
Aggregate (Küme)
Aggregate, tutarlılık sınırı oluşturan entity ve value object'lerin bir grubudur. Her Aggregate'in bir Aggregate Root'u (kök entity) vardır ve dış dünya yalnızca Aggregate Root aracılığıyla Aggregate ile etkileşime girer.
Aggregate Tasarım Kuralları
- Aggregate'ler küçük tutulmalıdır
- Aggregate'ler arası referanslar yalnızca ID üzerinden yapılmalıdır
- Tek bir transaction'da yalnızca bir Aggregate güncellenmelidir
- Aggregate sınırları iş kuralları tarafından belirlenir, teknik gereksinimler tarafından değil
Repository (Depo)
Repository, Aggregate'lerin kalıcı depolama ile etkileşimini soyutlayan tasarım kalıbıdır. Domain katmanı, verilerin nasıl saklandığını bilmez; yalnızca Repository arayüzünü kullanır. Bu sayede veritabanı teknolojisi değişse bile domain mantığı etkilenmez.
Domain Service (Alan Hizmeti)
Birden fazla Aggregate'i kapsayan veya tek bir Entity'ye doğal olarak ait olmayan iş mantığı Domain Service içinde yer alır. Örneğin, para transferi işlemi iki farklı Hesap Aggregate'ini etkilediğinden bir Domain Service'de tanımlanmalıdır.
Domain Event (Alan Olayı)
Domain Event, domain'de gerçekleşen önemli bir olayı temsil eder. "SiparişOluşturuldu", "ÖdemeAlındı", "ÜrünStokBitti" gibi olaylar domain event'leridir. Bu olaylar Bounded Context'ler arası iletişim için güçlü bir mekanizma sağlar ve eventual consistency modelini destekler.
DDD ve Mikroservis Mimarisi
DDD ve mikroservisler doğal bir uyum içindedir. Her Bounded Context, potansiyel bir mikroservis adayıdır. Ancak her Bounded Context'in ayrı bir mikroservis olması gerekmez; bazı durumlarda birden fazla context tek bir deployment unit'te barınabilir.
DDD ile Mikroservis Sınırlarını Belirleme
- Her mikroservis kendi Bounded Context'ini temsil etmelidir
- Context Map, servisler arası iletişim protokollerini tanımlar
- Anti-Corruption Layer, harici servislerle entegrasyonda koruma sağlar
- Domain Event'ler, servisler arası asenkron iletişimi mümkün kılar
Event Storming
Event Storming, Alberto Brandolini tarafından geliştirilen ve DDD projelerinde domain keşfi için kullanılan bir workshop tekniğidir. Geliştiriciler ve iş uzmanları bir araya gelerek, domain'deki olayları turuncu yapışkan notlara yazarlar ve zaman çizelgesine yerleştirirler.
Event Storming Adımları
- Domain Event'leri Belirleme: Sistemde gerçekleşen tüm olayları listeleme
- Komutları Tanımlama: Olayları tetikleyen eylemleri belirleme
- Aggregate'leri Keşfetme: Komutları işleyen ve olayları üreten yapıları bulma
- Bounded Context'leri Çizme: İlişkili aggregate'leri gruplandırma
- Sorunlu Noktaları İşaretleme: Belirsizlikleri ve çatışmaları not alma
DDD Uygulama Önerileri
DDD her proje için uygun değildir. Basit CRUD uygulamalarında DDD aşırı mühendislik (over-engineering) olabilir. DDD, karmaşık iş mantığına sahip projelerde en büyük değeri sağlar.
- Küçük başlayın: Tüm projeyi DDD ile yazmak yerine en karmaşık domain'den başlayın
- İş uzmanlarıyla sürekli iletişim halinde olun
- Ubiquitous Language'ı kodda yaşatın
- Aggregate sınırlarını çok geniş tutmaktan kaçının
- CQRS ve Event Sourcing gibi tamamlayıcı kalıpları ihtiyaca göre değerlendirin
Sonuç
Domain-Driven Design, karmaşık yazılım projelerinde iş mantığını doğru modellemenin ve sürdürülebilir mimari oluşturmanın anahtarıdır. Ubiquitous Language ile iletişim sorunlarını çözer, Bounded Context ile karmaşıklığı yönetilebilir parçalara böler ve taktik tasarım kalıplarıyla temiz, test edilebilir kod yazmanızı sağlar. DDD öğrenme eğrisi dik olsa da, karmaşık projelerde sağladığı değer bu yatırıma fazlasıyla değer.