Domain-Driven Design: Giriş ve Temeller
Domain-Driven Design'a kapsamlı giriş - temel kavramlar, yapı taşları, stratejik desenler ve DDD'yi yazılım geliştirmede ne zaman ve nasıl uygulayacağınıza dair pratik rehber
Özet
Domain-Driven Design (DDD), kod yapısını business domain mantığıyla hizalayarak karmaşık yazılım sistemleri oluşturmanın stratejik bir yaklaşımıdır. Bu rehber, DDD'nin temel kavramlarını, yapı taşlarını ve stratejik desenlerini, bu prensipleri ne zaman ve nasıl etkili bir şekilde uygulayacağınızı gösteren pratik TypeScript örnekleriyle birlikte inceliyor.
Domain-Driven Design Nedir?
Domain-Driven Design, Eric Evans tarafından 2003'te tanıtılan, teknik uzmanlar ve domain uzmanları arasındaki işbirliğini vurgulayan bir yazılım geliştirme yaklaşımıdır. Ana fikir: kodunuz, hizmet ettiği business domain'i yansıtmalı ve domain uzmanlarının kullandığı dil ve kavramları kullanmalıdır.
DDD ile çalışmak bana bunun sadece teknik pattern'lerle ilgili olmadığını öğretti—geliştiriciler ve business paydaşları arasında ortak bir anlayış oluşturmakla ilgili. Ubiquitous Language bu anlayışın taşıyıcısıdır; kod ve domain aynı terimleri kullandığında yanlış anlaşılmalar azalır. Kodunuz business'ınızla aynı dili konuştuğunda, iletişim gelişir ve yazılım daha sürdürülebilir hale gelir.
DDD'nin odaklandığı noktalar:
- Ubiquitous Language: Geliştiriciler ve domain uzmanları arasında paylaşılan ortak bir kelime dağarcığı
- Model-Driven Design: Business domain'i yansıtan kod yapısı
- Bounded Context'ler: Sistemin farklı bölümleri arasındaki net sınırlar
- Strategic Design: Büyük sistemleri organize etmek için üst düzey pattern'ler
- Tactical Design: Domain mantığını uygulamak için somut yapı taşları
DDD'yi Ne Zaman Kullanmalı (Ne Zaman Kullanmamalı)
DDD güçlü bir yaklaşım ama evrensel bir çözüm değil. Ne zaman mantıklı olduğuna dair öğrendiklerim.
DDD Kullanın:
Karmaşık Business Logic: Uygulamanız sık değişen karmaşık business kurallarına sahipse, DDD bu karmaşıklığı yönetmeye yardımcı olur. Business mantığının controller'lar ve servisler arasında dağıldığı codebas'ler gördüm - DDD bu kaosa yapı getirir.
Uzun Vadeli Projeler: Yıllarca sürdüreceğiniz sistemler için, DDD modellemesine yapılan ön yatırım karşılığını verir. Açık domain modeli yeni geliştiricilerin adapte olmasını hızlandırır ve değişiklikler sırasında business kurallarını bozma riskini azaltır.
İşbirlikçi Ortamlar: Domain uzmanları mevcut ve işbirliği yapmaya istekliyse, DDD parlıyor. Paylaşılan dil ve model, geleneksel geliştirme yaklaşımlarının başarmakta zorlandığı bir uyum yaratır.
Birden Fazla Bounded Context: Farklı alt alanlara sahip sistemler (örneğin, envanter, ödeme, sevkiyat içeren e-ticaret) DDD'nin sınırları ve ilişkileri yönetmek için stratejik pattern'lerinden faydalanır.
DDD'yi Atlayın:
Basit CRUD Uygulamaları: Minimal business mantığı olan basit bir veri giriş sistemi oluşturuyorsanız, DDD gereksiz karmaşıklık ekler. Temel bir MVC veya katmanlı mimari yeterlidir.
Prototip ve MVP'ler: Hızlı doğrulama projeleri için, DDD'nin modelleme yükü sizi yavaşlatır. Önce geri bildirim alın, proje büyürse DDD'yi düşünün.
Veri Odaklı Sistemler: ETL pipeline'ları, raporlama araçları ve analitik sistemleri genellikle domain modellemesi yerine veri odaklı yaklaşımlarla daha iyi hizmet verir.
Domain Uzmanlığı Olmayan Küçük Ekipler: Domain uzmanlarına erişemiyorsanız veya ekibiniz modelleme yatırımını haklı çıkaramayacak kadar küçükse, daha basit yaklaşımlar daha iyi çalışır.
Temel Yapı Taşları
DDD'nin yapı taşlarını oluşturan tactical pattern'leri inceleyelim. Bunlar kodunuzda kullanacağınız somut implementasyonlar.
Entity'ler
Entity'ler, zaman içinde devam eden benzersiz bir kimliğe sahip nesnelerdir. Aynı veriye sahip ancak farklı ID'lere sahip iki entity farklı nesnelerdir.
Temel özellikler:
- Kimlik:
idalanı kullanıcıyı benzersiz şekilde tanımlar - Yaşam Döngüsü: Entity'ler oluşturulur, değiştirilir ve sonunda silinebilir
- Factory method'lar: Yeni entity'ler için
create(), storage'dan yükleme içinreconstitute() - Business mantığı:
changeEmail()gibi method'lar domain kurallarını uygular
Value Object'ler
Value Object'ler kimliği olmayan kavramları temsil eder. Aynı veriye sahip iki value object eşit kabul edilir.
Value object'ler:
- Immutable: Setter yok, operasyonlar yeni instance'lar döner
- Kendi kendini doğrulayan: Veri geçersizse oluşturma başarısız olur
- Değiştirilebilir: Onları değiştirmezsiniz, yeni instance'larla değiştirirsiniz
- Değere göre karşılaştırılabilir: Eşitlik kimliğe değil veriye dayalıdır
Aggregate'ler
Aggregate'ler, net bir sınır ve tek bir root entity'ye sahip entity ve value object kümeleridir. Aggregate root tutarlılık kurallarını uygular.
Aggregate prensipleri:
- Tek Root: Dışarıdan sadece
Orderreferans edilir;OrderItemiçseldir - Tutarlılık Sınırı: Tüm business kuralları aggregate root tarafından uygulanır
- Transaction'al: Bir aggregate'e yapılan değişiklikler atomik olarak commit edilmelidir
- ID ile Referans: Diğer aggregate'ler buna ID ile referans verir, doğrudan nesne referansı ile değil
Repository'ler
Repository'ler, aggregate'lere erişim için bir soyutlama sağlar ve persistence detaylarını gizler.
Repository pattern'leri:
- Collection benzeri interface: Bunu in-memory bir collection olarak düşünün
- Persistence ignorance: Domain katmanı veritabanı detaylarını bilmez
- Aggregate odaklı: Her aggregate root için bir repository
- Test edilebilirlik: Test için implementasyonları kolayca değiştirebilirsiniz
Domain Service'ler
Domain service'ler, bir entity veya value object'e doğal olarak uymayan business mantığını içerir. Domain nesneleri üzerinde stateless operasyonlardır.
Domain service'leri ne zaman kullanmalı:
- Cross-aggregate operasyonlar: Birden fazla aggregate içeren mantık
- External sistem koordinasyonu: Birden fazla domain operasyonunu orkestre etme
- Karmaşık hesaplamalar: Birden fazla entity kullanan ama birine ait olmayan business mantığı
- Stateless operasyonlar: İç state yok, sadece dönüşümler
Stratejik Design Pattern'leri
Stratejik DDD pattern'leri büyük sistemleri organize etmeye ve karmaşıklığı daha üst düzeyde yönetmeye yardımcı olur.
Ubiquitous Language
Ubiquitous Language, geliştiriciler ve domain uzmanları arasındaki paylaşılan kelime dağarcığıdır. Bu dil kod, konuşmalar, dokümantasyon ve testlerde görünür.
İşte öğrendiğim: kodunuz business paydaşlarınızdan farklı terimler kullandığında, çeviri hataları sızar. Business buna "rezervasyon" derken kodunuz "booking" diyorsa, birisi gereksinimleri yanlış anlayacaktır.
Kötü Örnek (Genel teknik terimler):
İyi Örnek (Ubiquitous Language):
Pratikte, ubiquitous language oluşturmak şu anlama gelir:
- Domain uzmanlarıyla işbirlikçi modelleme oturumları
- Business ve teknoloji arasında paylaşılan terim sözlüğü
- Kod, doküman ve konuşmalarda tutarlı isimlendirme
- Anlayış derinleştikçe zaman içinde iyileştirme
Bounded Context'ler
Bounded Context, bir domain modelinin tanımlandığı ve uygulandığı açık bir sınırdır. Farklı context'ler aynı kavram için farklı modellere sahip olabilir.
Bir e-ticaret sisteminde "Customer"ı düşünün:
Kod'da bunlar farklı görünebilir:
Bounded context'lerin faydaları:
- Odaklanmış modeller: Her context sadece ihtiyacı olanı içerir
- Bağımsız evrim: Context'ler diğerlerini etkilemeden değişebilir
- Net sahiplik: Ekipler belirli context'lere sahiptir
- Azaltılmış coupling: Bağımlılıklar context sınırlarında açıktır
Context Mapping
Context Mapping, bounded context'ler arasındaki ilişkileri tanımlar. Yaygın pattern'ler:
Customer/Supplier: Downstream context upstream'e bağlıdır. Ekipler değişiklikleri müzakere eder.
Anti-Corruption Layer: Modelinizi harici sistem kavramlarından korur.
Shared Kernel: İki context domain modelinin bir alt kümesini paylaşır. Değişiklikler koordinasyon gerektirir.
Yaygın Tuzaklar ve Nasıl Kaçınılır
DDD ile çalışırken, bu sorunlarla tekrar tekrar karşılaştım:
Anemic Domain Model'ler
Sorun: Entity'ler getter/setter'lara sahip veri containerları haline gelir, tüm mantık service'lerde.
Çözüm: Davranışı domain modeline taşıyın.
Basit Domain'leri Aşırı Mühendislik
Sorun: Basit CRUD operasyonlarına tam DDD uygulamak.
Basit bir adres defteri oluşturuyorsanız, aggregate'lere, repository'lere ve domain service'lere ihtiyacınız yok. Validation ile basit bir veri modeli yeterlidir. DDD'yi karmaşık business mantığı için saklayın.
Context Sınırlarını Görmezden Gelmek
Sorun: Tüm sistemi tek bir büyük model olarak ele almak.
Bu, her kullanım durumuna hizmet etmeye çalışan 50 özelliğe sahip Customer gibi god object'lere yol açar. Bunun yerine, "customer"ın sales, support ve billing context'lerinde farklı anlamlara geldiğini kabul edin.
Repository'yi Database Gateway Olarak Kullanmak
Sorun: Her kullanım durumu için sorgu methodları sunan repository'ler.
Çözüm: Repository'leri aggregate root'lara odaklı tutun. Sorgular için ayrı read model'ler kullanın.
Büyük Aggregate'ler
Sorun: Çok büyüyen ve performans sorunlarına neden olan aggregate'ler.
Order aggregate'iniz customer detaylarını, sevkiyat bilgilerini, ödeme geçmişini ve ürün kataloglarını içeriyorsa, basit operasyonlar için çok fazla veri yüklersiniz.
Çözüm: Aggregate'leri küçük ve odaklı tutun. Diğer aggregate'lere ID ile referans verin.
Ubiquitous Language'ı Atlamak
Sorun: Geliştiriciler business dilini kullanmak yerine kendi teknik terimlerini yaratır.
Bu, hataların gizlendiği bir çeviri katmanı oluşturur. Kod "transaction processing" derken business "payment confirmation" dediğinde, yanlış anlamalar oluşur.
Çözüm: İşbirlikçi modelleme oturumlarına zaman yatırın. Kodunuz domain uzmanlarınızın kullandığı kesin terimleri kullanmalıdır.
Pratik Uygulama İpuçları
Pratikte işe yarayanlar:
Küçük Başlayın: Her şeyi birden ele almayın. Bir karmaşık subdomain seçin ve DDD'yi orada uygulayın. Genişletmeden önce ekibiniz için neyin işe yaradığını öğrenin.
Event Storming: Geliştiricilerin ve domain uzmanlarının yapışkan notlarla business süreçlerini haritaladığı işbirlikçi oturumlar düzenleyin. Bu, ubiquitous language'ı ve bounded context'leri doğal olarak ortaya çıkarır.
Test-Driven Development: Testleri business dilinde yazın. Bu, domain modelini güçlendirir ve kodun business niyetinden ayrıldığını yakalar.
Aşamalı Refactoring: Her şeyi yeniden yazmanıza gerek yok. Domain mantığını service'lerden entity'lere kademeli olarak çıkarın. Her iyileştirme bir sonrakini kolaylaştırır.
Kod Olarak Dokümantasyon: Domain kavramlarını belgelemek için type'lar ve interface'ler kullanın. Kendi kendini belgeleyen kod, eskiyen harici doküman ihtiyacını azaltır.
Kaynaklar ve İleri Okuma
DDD anlayışınızı derinleştirmek için temel kaynaklar:
Kitaplar:
- "Domain-Driven Design" by Eric Evans - Orijinal mavi kitap. Yoğun ama kapsamlı. Yapı taşlarıyla ilgili Bölüm II ile başlayın.
- "Implementing Domain-Driven Design" by Vaughn Vernon - Daha pratik ve modern. Implementasyon rehberliği için mükemmel.
- "Domain-Driven Design Distilled" by Vaughn Vernon - Yoğunlaştırılmış giriş, hızlı başlamak için iyi.
Online Kaynaklar:
- Martin Fowler'ın makaleleri martinfowler.com'da - Temel pattern'lerin net açıklamaları
- DDD Community ddd-community.org'da - Kaynaklar ve etkinliklerle aktif topluluk
- Alberto Brandolini'nin EventStorming websitesi - İşbirlikçi modelleme tekniği
Pratik Örnekler:
- Microsoft'un .NET Microservices Architecture rehberi - Pratikte iyi DDD pattern'leri
- GitHub'da çeşitli dillerde DDD implementasyonlarını gösteren örnek projeler
Sonuç
Domain-Driven Design, yazılım sistemlerindeki karmaşıklığı yönetmek için güçlü araçlar sağlar. Tactical pattern'ler - entity'ler, value object'ler, aggregate'ler, repository'ler ve domain service'ler - size temiz domain modelleri için yapı taşları verir. Stratejik pattern'ler - ubiquitous language, bounded context'ler ve context mapping - büyük sistemleri organize etmeye yardımcı olur.
İşte en çok önemsediğim şeyleri öğrendim: DDD körü körüne pattern'leri uygulamakla ilgili değil. Business ve teknoloji arasında ortak bir anlayış oluşturmak, sonra bu anlayışı kodda ifade etmekle ilgili. Kodunuz business dilini konuştuğunda, modelleriniz domain uzmanlarının problemler hakkında düşünme şeklini yansıttığında, yazılımın anlaşılması, sürdürülmesi ve evrimleşmesi daha kolay hale gelir.
Bir karmaşık subdomain ile başlayın. Domain uzmanlarıyla işbirliği yapın. Birlikte ubiquitous language oluşturun. Tactical pattern'leri değer kattıkları yerde uygulayın. Sisteminiz büyüdükçe bounded context'leri tanıyın. DDD bir yolculuktur, bir hedef değil - bu prensipleri gerçek projelerde uygularken anlayışınız derinleşecektir.