DDD Tactical Patterns Notları

design-patternstrendyoltechdomain-driven-designentitycqrs
 DDD Tactical Patterns Notları

🎙️ Giriş

Trendyol Tech kanalında 9 Mayıs 2024 tarihinde paylaşılan Domain Driven Design: Tactical Patterns yayınından çıkarılmış notları içerir.

⚠️ Neden Bu Pattern’ler?

Domain-Driven Design (DDD), karmaşık iş kurallarını doğru modellemek için önemli bir rehberdir. DDD’nin Tactical kısmında, domain modelinizi doğru şekilde nasıl tasarlayabileceğinizi gösteren bu pattern’ler yer alır. Özellikle Anemic Model ve Rich Model farkını anlamak çok kritik.

📊 Anemic Model Nedir?

  • Sadece veri taşır.
  • İş kuralları ve validasyonlar dışarıda (genellikle service’lerde) tutulur.
  • Encapsulation ihlal edilir.
  • Single Responsibility prensibine aykırıdır.
  • Business logic servis katmanına sızar, bu da bakımı ve anlaşılmasını zorlaştırır.

✅ Rich Domain Model Nedir?

  • Veri ve davranış bir aradadır.
  • Encapsulation sağlar.
  • Nesne, kendi geçerliliğini (validity) kendi kontrol eder.
  • Servis katmanı, modelin iç detaylarını bilmek zorunda kalmaz.
  • Okunabilirlik, bakım ve genişletilebilirlik artar.

💎 Value Object Nedir?

  • Kimlik taşımaz (ID’sizdir).
  • Immutable’dır, oluşturulduktan sonra değişmez.
  • Kendi kendini valide eder.
  • Aynı değerlere sahip iki value object eşittir.
  • Örnek: Para birimi, mesafe, telefon numarası gibi kavramlar.

📦 Entity Nedir?

  • Kimlik (ID) taşır.
  • Kimlik üzerinden takip edilir.
  • Değişebilir (mutable) olabilir.
  • Aynı değerlere sahip iki entity, farklı kimliklere sahipse farklıdır.

🌐 Aggregate & Aggregate Root

  • Birden fazla entity ve value object’i kapsayan bütün.
  • Aggregate Root, dış dünya ile iletişimi yönetir.
  • Aggregate root dışındaki entity’lere direkt erişilemez.
  • Tek seferde yüklenir ve kaydedilir (atomicity sağlar).
  • Data consistency’yi garanti eder.
  • Versioning (Optimistic Locking) ile aynı anda birden fazla değişikliği engeller.

⚒️ Effective Aggregate Design

  • Large Cluster Aggregates: Çok fazla entity içerir, conflict riski yüksektir.
  • Multiple Small Aggregates: Fazla parçalanırsa yönetimi zorlaşır.
  • Optimal: Doğru invariant’ları kapsayan, gerektiği kadar kapsamlı aggregate’ler.
  • Aggregates arası iletişim sadece ID üzerinden olmalıdır.
  • Eventually Consistent yapılarla da desteklenebilir.
  • One Aggregate per Transaction prensibi önerilir.

📡 Domain Events

  • Aggregate içinde meydana gelen önemli olayları dış dünyaya bildirir.
  • Aggregates arası gevşek bağlı (loose coupling) iletişimi sağlar.
  • Event Handler’lar application seviyesinde konumlanır.
  • Event Payload sadece gerekli bilgiyi taşır, consumer’a özel veri eklenmez.
  • Outbox Pattern ile event’lerin kaybolması engellenir.

🏭 Factory

  • Aggregate, entity veya value object üretiminden sorumludur.
  • Karmaşık oluşturma senaryolarını kapsar.
  • External sistemlerle entegrasyonu da yönetebilir.
  • Modelin creation logic’ini ve business logic’ini birbirinden ayırır.

🛠️ Domain Service

  • Birden fazla entity veya value object ile çalışan servislerdir.
  • State tutmaz.
  • Genellikle entity’ler arası karşılaştırma, toplama ve işlem yapma gibi durumlarda kullanılır.
  • Rich Model yeterliyse, domain service ihtiyacı azalır.
  • Anemic Model riskine dikkat edilmelidir.

📑 CQRS (Command Query Responsibility Segregation)

  • Command: Veri değiştirir (insert, update, delete).
  • Query: Veri getirir (read).
  • Read ve write taraflarını ayırarak performans ve scalability sağlar.
  • Eventually Consistent modellerle uyumludur.
  • Command ve Query Handler’lar, tek bir sorumluluğu olan küçük parçalardır.

🔗 Event Sourcing

  • Son durumu değil, tüm olayları saklar.
  • Projector ile event’lerden read model (son durum) oluşturulur.
  • Tüm değişiklikler geriye dönük izlenebilir.
  • Append-Only yapısı performans avantajı sağlar.
  • Event Store yönetimi, versiyonlama ve schema evrimi gibi ek zorlukları vardır.

⚖️ Separation of Concerns

  • Büyük servisler yerine küçük command’lar ve handler’lar önerilir.
  • Kolay test edilebilir.
  • Error management ve transaction yönetimi event-driven olabilir.
  • Chain of Responsibility veya Pipeline Patterns ile adımlar modülerleştirilebilir.

📐 Hexagonal Architecture (Ports & Adapters)

  • Alistair Cockburn tarafından önerilmiştir.
  • Amaç: Uygulamanın çekirdeği ile dış dünyaya olan bağımlılıkları ayırmak.
  • İç Çekirdek (Core): Domain ve Application Logic içerir.
  • Port: Uygulamanın dış dünyayla iletişim kurmak için sunduğu arayüzlerdir.
  • Adapter: Port’ların dış dünyayla (DB, API, UI, vs.) nasıl iletişim kuracağını tanımlar.
UI / Rest / CLI
|
Adapter
|
Port
|
Application Service
|
Domain
  • ✅ Bağımsız test edilebilirlik
  • ✅ Teknoloji bağımlılığı minimum
  • ✅ Net sınırlar ve sorumluluklar

🔗 Distributed Transactions

  • Dağıtık sistemlerde birden fazla servis ve veri kaynağına dokunan işlemlerde transaction yönetimi zordur.
  • ACID garantisi vermek zorlaşır.
  • Çözüm Yolları:
  • ✅ Two-Phase Commit (2PC)
  • ✅ Saga Pattern
  • ✅ Outbox Pattern + Eventual Consistency
  • ✅ Compensation Transactions (Tersine İşlemler)

✅ Two-Phase Commit (2PC)

  • Dağıtık transaction yönetiminde klasik bir yaklaşımdır.
  • Transaction Coordinator tüm katılımcıları yönetir.
  • Aşamalar:
  1. Prepare Phase: Tüm katılımcılara “Hazır mısın?” sorulur.
  2. Commit Phase: Tüm katılımcılar “Hazırım” derse commit edilir. Bir hata varsa rollback yapılır.
Avantajlar
✅ ACID garantisi sağlar.
Dezavantajlar
❌ Performans problemi (bekleme süresi uzun).
❌ Tek noktaya bağımlılık (Transaction Coordinator).
❌ Mikroservis dünyasında önerilmez.

🔄 Saga Pattern

  • Büyük bir işlemi, birbirini takip eden local transaction zincirine böler.
  • Her adım kendi local transaction’ını yapar ve bir event fırlatır.
  • Event’i dinleyen bir sonraki servis kendi local transaction’ını başlatır.

Örnek Akış

  1. Order Service siparişi oluşturur (local transaction).
  2. Order Service, Payment Service’e “Ödeme Al” event’i yollar.
  3. Payment Service ödeme alır (local transaction), sonra Shipping Service’e event yollar.
  4. Shipping Service kargoyu başlatır (local transaction).

Avantajlar

  • ✅ Esnek ve scalable.
  • ✅ Event-Driven sistemlere uygundur.

Dezavantajlar

  • ❌ Eventual consistency problemi.
  • ❌ Hata yönetimi ve retry mekanizmaları karmaşık olabilir.
  • ❌ Observability (distributed tracing) kritik hale gelir.
Karşılaştırma

🌟 Öneri Özet

  • Monolitik veya tek bir veri kaynağına dokunan işlemler: 2PC.
  • Mikroservisler ve asenkron süreçlerde: Saga Pattern (tercihen Choreography).
  • Kullanıcı deneyimi çok kritikse ve hızlı geri bildirim gerekiyorsa: Outbox + Eventual Consistency.

🔥 Son Notlar & Örnekler

  • Her domain event, sadece bir durumu yansıtmalı.
  • Consumer’a özel veri taşıyan event’lerden kaçınılmalı.
  • Domain event’ler başka bounded context’lere doğrudan gönderilmemeli (anti-pattern).
  • Domain event’ler kaybolmamalı, Outbox Pattern kullanılmalı.
  • Transactional boundary aggregate’lere göre belirlenmeli.
  • Bir işlemde birden fazla aggregate dokunuyorsa, bu genellikle tasarım hatasıdır.