Oyun Kitabı

DDD'nin Kodu: Entity, Value Object ve Aggregate (Dddnin Kodu Entity Value Object Ve Aggregate)

DDD taktiksel desenleri nasıl çalışır? Value Object, Entity, Aggregate, Domain Service, Application Service ve Repository sınırlarını gerçek sipariş örneğiyle…

DDD tactical patterns showing value objects, entities, and aggregate boundaries

Bu yazının sorusu

İlk bölümde işin dili ve kurallarıyla başladık. Şimdi soru şudur: Bu kararları kodun içinde nerede tutacağız? Aynı e-ticaret domainini kullanacağız: para, sipariş satırı, sipariş ve ödeme akışı.

Money + Address + Quantity → Value Object
OrderLine + Order → Entity
Order (Root) + OrderLine → Aggregate
Repository → Aggregate Root'u yükler ve kaydeder

Kavramlar ilk geçtiği yerde

📦 Value Object
Kimliği olmayan, değeriyle tanımlanan ve değişmez tutulması gereken kavram.

📦 Entity
Nitelikleri değişse de kimliği boyunca aynı kalan nesne.

📦 Aggregate
Birlikte tutarlı kalması gereken nesnelerin transaction sınırı.

📦 Aggregate Root
Aggregate'e dışarıdan girilebilen tek kapı.

Örneğin iki Money(100, "TRY") aynı değeri temsil eder; iki sipariş ise toplamları aynı olsa bile farklı kimliklere sahiptir.

Value Object ile başlayın

DDD'de düşünme biçimini en iyi Value Object öğretir. decimal tek başına para değildir: para birimi, yuvarlama, indirim ve vergi kuralları taşır. Bu kuralları servis katmanına dağıtmak yerine kavramın içinde saklayın.

public sealed record Money(decimal Amount, string Currency)
{
    public Money Add(Money other)
    {
        if (Currency != other.Currency) throw new DomainException("Currency mismatch");
        return new Money(Amount + other.Amount, Currency);
    }
}

Value Object değişmezdir: değer değiştiğinde yeni örnek oluşur. Böylece aynı adres, e-posta, tarih aralığı veya para değeri farklı işlem adımlarında beklenmedik yan etki üretmez. Karşılaştırma içeriğe göre yapılır; bu nedenle eşitlik maliyeti alan sayısıyla O(m), fakat iş kuralı tek yerde kaldığı için değişiklik maliyeti düşer.

Entity: kimlik ve yaşam döngüsü

Order bir Entity'dir. Adresini veya toplamını değiştirebilir; yine de aynı sipariştir. Fark, setter kullanmakta değil, geçişi iş davranışı olarak modellemektedir.

❌ order.Status = Paid
✓ order.ConfirmPayment(payment)

❌ order.Total = total - discount
✓ order.ApplyDiscount(discount)

ConfirmPayment ödeme kanıtını, siparişin iptal edilmediğini ve geçişin yasal olduğunu tek noktada kontrol eder. Entity'nin görevi her veriyi taşımak değil, kendi yaşam döngüsündeki geçersiz durumları engellemektir.

Aggregate: nesne grubu değil, tutarlılık sınırı

Sipariş ve satırları aynı transaction içinde tutarlı kalmalıdır: satır sayısı pozitif, toplam satırların toplamına eşit ve sipariş onaylandıktan sonra satır değiştirilemez olabilir. Bu yüzden Order, OrderLine için Aggregate Root olur.

Order aggregate
  ├─ OrderLine
  ├─ ShippingAddress
  └─ Total

Dış dünya → Order.AddLine() / Order.ConfirmPayment()
Dış dünya ↛ OrderLine'a doğrudan yazmaz

Aggregate'i büyük yapmak güvenlik değil, kilit ve versiyon çakışması üretir. Yazma isteğinde mümkün olduğunca tek Aggregate güncelleyin. Başka Aggregate'e nesne referansı yerine ID ile bağlanın; koordinasyon gerekiyorsa domain event veya application orchestration kullanın. Bu, optimistic concurrency çakışmalarını ve gereksiz yükleme maliyetini sınırlar.

Domain Service ve Application Service

Bir kural doğal olarak tek Aggregate'e ait değilse Domain Service olabilir: örneğin döviz kuru üzerinden fiyatlama. Buna karşılık Application Service use case'i yönetir: siparişi yükler, davranışı çağırır, kaydeder ve transaction'ı tamamlar.

Katman Sorumluluk
Value Object / Entity / Aggregate İş kuralını ve invariant'ı korumak
Domain Service Aggregate'e ait olmayan saf domain hesabı
Application Service Use case orkestrasyonu, transaction ve adaptör çağrıları

Application Service içine kural koymak, anemic modelin servis katmanına geri dönmesidir.

Repository: tablo API'si değil

Repository, domain'e bellek içi koleksiyon hissi verir; SQL veya ORM ayrıntısı değildir. Her tablo için repository üretmek yerine yalnızca Aggregate Root'lar için kullanın. OrderLineRepository.Find() ile alt varlığı doğrudan değiştirmek Root'un koruduğu kuralları baypas eder.

order = orderRepository.get(orderId)
order.confirmPayment(payment)
orderRepository.save(order)

Bu sınır, testte sahte repository ile domain davranışını altyapıdan bağımsız doğrulamayı da sağlar. ORM'nin boş constructor veya eager-loading zorunluluğu domain modelini şekillendirmemelidir; mapping adaptörün sorumluluğudur.

Yanlış güven duygusu oluşturan eşleştirmeler

❌ Value Object = küçük DTO
✓ Value Object değer, doğrulama ve davranış taşır.

❌ Aggregate = mümkün olduğunca büyük nesne grafiği
✓ Aggregate = küçük tutulmuş transaction ve consistency boundary.

❌ Domain Service = iş kuralı çöplüğü
✓ Yalnızca tek Aggregate'e ait olmayan saf domain davranışı.

❌ Repository = her tablo için CRUD API
✓ Repository = Aggregate Root'un kalıcılık sınırı.

Uygulama kontrol listesi

  1. Money, Email, Address, Quantity gibi kavramlar primitive olarak mı dolaşıyor?
  2. Her Entity davranışı geçersiz bir durumu gerçekten engelliyor mu?
  3. Bir yazma isteği birden fazla Aggregate'i atomik güncellemeye çalışıyor mu?
  4. Application Service karar mı veriyor, yoksa yalnızca orkestrasyon mu yapıyor?
  5. Repository yalnızca Aggregate Root'ları mı yüklüyor?

Önce en pahalı iş kuralından başlayın; tüm sistemi bir anda taktiksel DDD'ye dönüştürmeyin.

Bu yazıdan aklında ne kalmalı

Value Object kavramın değerini ve kurallarını taşır. Entity kimliği ve yaşam döngüsünü korur. Aggregate, tutarlılık için küçük bir transaction sınırı çizer. Repository ise bu sınırı kalıcılıkla buluşturur.

Taktiksel DDD'nin amacı daha fazla sınıf değil, iş kuralının yanlış katmana sızmasını engellemektir.

Bir sonraki bölümde bu sınırları büyük sistemde nasıl konuşacağımızı inceleyeceğiz: Bounded Context, Context Mapping ve monolith içindeki ayrışma.

SSS

Sık sorulan sorular

Value Object nedir?

Kimliği olmayan, değeriyle tanımlanan ve değişmez tutulması gereken kavram.

Entity nedir?

Nitelikleri değişse de kimliği boyunca aynı kalan nesne.

"Value Object = küçük DTO" doğru mu?

Value Object değer, doğrulama ve davranış taşır.

Bu bölüm neyi sabitler?

İlk bölümde işin dili ve kurallarıyla başladık. Şimdi soru şudur: Bu kararları kodun içinde nerede tutacağız? Aynı e-ticaret domainini kullanacağız: para, sipariş satırı, sipariş ve ödeme akışı. İlk bölümde işin dili ve kurallarıyla başladık. Şimdi soru şudur: Bu kararları kodun içinde nerede tutacağız? Aynı e-ticaret domainini kullanacağız: para, sipariş satırı, sipariş ve ödeme akışı.

Ogrenilen Muhendislik Prensipleri

  • Value Object, primitive veriyi iş anlamı ve doğrulamayla birleştirir.
  • Aggregate küçük ve açık bir tutarlılık sınırı olmalıdır.
  • Application Service süreç yönetir; domain davranışı karar verir.

Okumaya devam et

Okumaya devam et

Ilgili yazilar

Ilgili yazilar

Ilgili yazilar

Paylaş