# 🧠 Mikroservislerde Rich Domain Modelleri: DDD ve CQRS ile Karmaşayı Ehlileştirin

Mikroservis mimarileri, günümüz yazılım dünyasının parlayan yıldızı. Esneklik, ölçeklenebilirlik ve bağımsız geliştirme imkanı sunarken, karmaşık iş mantığını yönetmek ve sürdürülebilir bir kod tabanı oluşturmak çoğu zaman büyük bir meydan okuma haline gelebilir. Her şeyin sadece veri tabanına yazıp okumaktan ibaret olduğu "**Anemic Model**" yaklaşımlarıyla baş etmek, uzun vadede sürdürülemez bir kod tabanına ve teknik borca yol açabilir.

Peki, bu karmaşayı nasıl dizginleyebiliriz? Cevap: **Domain-Driven Design (DDD)** prensiplerini uygulayarak ve mikroservislerinizi **Rich Domain Model** ile donatarak. Gelin, bu yolculuğa birlikte çıkalım!

---

## 🧱 **Rich Domain Model** Nedir ve Neden Gereklidir?

Basitçe ifade etmek gerekirse, **Rich Domain Model**, sadece veri tutan nesnelerden ibaret değildir; aynı zamanda bu veriler üzerindeki **iş kurallarını ve davranışları** da içinde barındırır. Her **Bounded Context (Sınırlı Bağlam)** veya mikroservis için tek ve bütüncül bir **Domain Model** tanımlamak esastır.

Diyelim ki bir e-ticaret sistemindeki `Order` (Sipariş) nesnesinden bahsediyoruz. Anemik bir modelde `Order`, yalnızca `OrderId`, `CustomerId`, `TotalAmount` gibi alanlara sahip basit bir veri yapısı olurdu. Siparişle ilgili tüm iş kuralları (ürün ekleme, indirim hesaplama, stok kontrolü vb.) ise genellikle ayrı **Service Layer** (Servis Katmanı) içinde (örneğin bir `OrderService` içinde) bulunurdu.

Rich bir modelde ise `Order` sınıfı, `AddItem(Product product, int quantity)` veya `ApplyDiscount(DiscountCode code)` gibi metotlara sahip olurdu. Bu metotlar, siparişle ilgili tüm iş mantığını ve kurallarını (örneğin, `quantity` sıfırdan küçük olamaz gibi) kendi içinde barındırırdı.

### **Rich Domain Model** vs. **Anemic Model**: Temel Farklar

Bu ayrım, yazılımın karmaşıklığı arttıkça hayati hale gelir:

| Özellik | **Rich Domain Model** | **Anemic Model** |
| --- | --- | --- |
| **Data + Behavior** (Veri + Davranış) | Bir arada, **Entity** içinde | Ayrı; davranışlar **Service Layer**'da |
| **OOP Principles** (OOP Prensipleri) | ✅ Kapsülleme ve sorumluluklar net | ❌ Daha çok prosedürel yaklaşım |
| **Application Complexity** (Uygulama Karmaşıklığı) | Yüksek, ancak uzun vadede sürdürülebilir | Düşük, basit CRUD için yeterli olabilir |
| **Recommended Scenario** (Tavsiye Edilen Senaryo) | Karmaşık **Domain**'lerde uzun vadeli bakım için | Basit servislerde (örneğin sadece CRUD işlemleri) |

Rich **Domain Model**'ler, iş kurallarının kod içinde açıkça ifade edilmesini, test edilebilirliğin artmasını ve sistemin daha kolay sürdürülebilir olmasını sağlar.

---

## 🧩 DDD'nin Temel Yapı Taşları: **Value Object**, **Entity** ve **Aggregate**

Rich **Domain Model**'i inşa ederken, **Domain-Driven Design (DDD)**'ın temel yapı taşlarını doğru anlamak çok önemlidir.

### **Value Object** (Değer Nesnesi) & **Entity** (Varlık) Ayrımı

* **Value Object:** Kimliği olmayan, sadece **anlam taşıyan** nesnelerdir. İki **Value Object**, içerikleri aynıysa birbirine eşittir. Örneğin, bir `Address` (Adres) (sokak, şehir, posta kodu), bir `Money` (Para) (miktar, para birimi) veya `EmailAddress` (E-posta Adresi) bir **Value Object** olabilir. "Kim" olduğundan ziyade "ne olduğu" önemlidir.
    
    ```csharp
    public record Address(string Street, string City, string ZipCode);
    ```
    
    Burada iki `Address` nesnesi aynı `Street`, `City` ve `ZipCode` değerlerine sahipse, onlar eşittir.
    
* **Entity:** Kimliği olan, zamanla değişebilen ve yaşam döngüsü boyunca bir **benzersizliğe** sahip olan nesnelerdir. Bir `User` (Kullanıcı), `Order` (Sipariş) veya `Product` (Ürün) bir **Entity**'dir. **Entity**'nin kimliği (genellikle bir ID alanı), değerleri değişse bile sabit kalır. "Ne olduğu"ndan ziyade "kim olduğu" önemlidir.
    
    ```csharp
    public class User {
        public Guid Id { get; set; }
        public string FullName { get; set; }
        public string Email { get; set; }
    }
    ```
    

### 🏘️ **Aggregate** (Topluluk) ve **Aggregate Root** (Topluluk Kökü) Kavramı

İşte kafa karıştırıcı olabilecek ama en kritik DDD kavramlarından biri!

* **Aggregate:** Birbirine sıkı sıkıya bağlı **Entity** ve **Value Object**'lerin mantıksal bir kümesidir. Bu küme, **Domain** içindeki bir **Consistency Boundary** (Tutarlılık Sınırı)'nı temsil eder. Örneğin, bir `Order` (Sipariş), içindeki `OrderItem`'lar (Sipariş Kalemleri) ile birlikte bir **Aggregate** oluşturur.
    
* **Aggregate Root:** Bu **Aggregate** kümesinin dış dünyayla tek erişim noktası, yani "**kapı bekçisidir**." **Aggregate** içindeki diğer nesneler (**Child Entity**'ler veya **Value Object**'ler) doğrudan dışarıdan manipüle edilemez; tüm işlemler **Aggregate Root** üzerinden yapılır.
    
    **Amaç:** Tutarlılığı korumak ve **Domain Boundary** (Alan Sınırı)'nı belirgin kılmaktır. Bir işlem sırasında, **Aggregate** içindeki tüm nesnelerin birlikte tutarlı kalması sağlanır. Bu, aynı zamanda **Transaction Boundary** (İşlem Sınırı)'nı da tanımlar.
    

**Örnek: Order Aggregate ve Aggregate Root (.NET)**

Bir siparişi yöneten **Aggregate**'i düşünelim:

```csharp
public interface IAggregateRoot {}

// Aggregate Root
public class Order : Entity, IAggregateRoot // IAggregateRoot bir işaretleyici arayüz olabilir
{
    private List<OrderItem> _orderItems = new();
    public IReadOnlyCollection<OrderItem> OrderItems => _orderItems.AsReadOnly(); // Dışarıdan sadece okunabilir

    public DateTime OrderDate { get; private set; }
    public Address ShippingAddress { get; private set; } // Value Object

    public Order(Address shippingAddress)
    {
        ShippingAddress = shippingAddress;
        OrderDate = DateTime.UtcNow;
    }

    // OrderItem'lar sadece Order üzerinden yönetilir
    public void AddItem(int productId, string name, decimal price, int quantity)
    {
        if (quantity <= 0) 
            throw new ArgumentException("Quantity must be positive.");

        var existingItem = _orderItems.FirstOrDefault(i => i.ProductId == productId);
        if (existingItem is not null)
            existingItem.IncreaseQuantity(quantity); // İş kuralı OrderItem içinde
        else
            _orderItems.Add(new OrderItem(productId, name, price, quantity));
    }
    // Diğer iş metotları (örneğin CompleteOrder, CancelOrder vb.)
}

// Child Entity (Aggregate içindeki)
public class OrderItem : Entity
{
    public int ProductId { get; private set; }
    public string ProductName { get; private set; }
    public decimal UnitPrice { get; private set; }
    public int Quantity { get; private set; }

    public OrderItem(int productId, string productName, decimal price, int quantity)
    {
        ProductId = productId;
        ProductName = productName;
        UnitPrice = price;
        Quantity = quantity;
    }

    public void IncreaseQuantity(int amount)
    {
        // Kendi iş kuralı
        if (amount <= 0) throw new ArgumentException("Amount must be positive.");
        Quantity += amount;
    }
}
```

Yukarıdaki örnekte:

* `Order` sınıfı **Aggregate Root**'tur.
    
* `OrderItem`'lar, `Order` **Aggregate**'i içindeki **Child Entity**'lerdir.
    
* `OrderItem`'lara doğrudan dışarıdan ulaşıp ekleme/silme yapılmaz; `Order`'ın `AddItem` metodu aracılığıyla yönetilirler. Bu, kuralların (örneğin `Quantity` artışı) tek bir yerde kontrol edilmesini ve **consistency (tutarlılığın) sağlanmasını** garantiler.
    

---

## 🌍 **Bounded Context** (Sınırlı Bağlam): Mahallelerin Ayrımı

**Bounded Context**, DDD'nin belki de en önemli kavramlarından biridir ve mikroservis mimarisinde doğrudan karşılığı vardır. Bir kavramın veya terimin sadece **o bağlamda (context) belirli bir anlamı olduğu** anlamına gelir. Farklı bağlamlarda aynı terim, farklı anlamlara veya farklı **Model**'lere sahip olabilir.

**Örnek: "User" (Kullanıcı) Kavramı**

Bir e-ticaret sistemini düşünelim:

* **Identity (Kimlik) Microservice (Mikroservis):** Burada bir `User` **Entity**'si, kullanıcının `Email` (E-posta), `PasswordHash`, `FullName` (Tam Adı), `NationalId` (Kimlik Numarası) gibi tüm kimlik doğrulama ve profil bilgilerini içerebilir.
    
    ```csharp
    // Identity Context - Kullanıcı detayları odaklı
    public class User
    {
        public int Id { get; set; }
        public string FullName { get; set; }
        public string Email { get; set; }
        public string NationalId { get; set; } // Kimlik bilgisi
        public string PasswordHash { get; set; }
        // ... Diğer kimlik bilgileri
    }
    ```
    
* **Ordering (Sipariş) Microservice:** Sipariş mikroservisinin bir siparişi kimin verdiğini bilmesi gerekir, ancak kullanıcının tüm kimlik bilgilerine (şifresi, kimlik numarası vb.) ihtiyacı yoktur. Burada `User` yerine `Buyer` (Alıcı) adında bir **Entity** tanımlarız ve sadece siparişle ilgili bilgileri (örneğin `UserId`, `FullName`, `ShippingAddress`) içerir.
    
    ```csharp
    // Ordering Context - Sipariş için sadece gerekli bilgiler
    public class Buyer
    {
        public int Id { get; set; } // Bu, Identity Context'teki User.Id ile eşleşir
        public string FullName { get; set; }
        // Siparişle ilgili diğer bilgiler, örn: PaymentMethod, PreferredShippingOption
    }
    ```
    
    Aynı kişi, iki farklı "**Bounded Context**"te iki farklı **Entity** olarak modellenmiştir. Her bağlam (**Context**) sadece kendi ihtiyaçlarını bilir ve kendi **Domain Model**'ini oluşturur. Bu, mikroservislerin **independence (bağımsızlığını)** ve **loose coupling (gevşek bağlılığını)** destekler.
    

## 🧠 **Service** (Hizmet): Orkestrasyon ve Çapraz Alan İş Mantığı

**Service**'ler, doğrudan bir varlığa ait olmayan veya birden fazla **Aggregate**'i ilgilendiren iş mantığını barındırmak için kullanılır. DDD'de iki ana **Service** türü bulunur: **Domain Service** (Alan Hizmeti) ve **Application Service** (Uygulama Hizmeti).

#### 🔸 **Domain Service**: **Domain Layer**'daki (Alan Katmanı) İş Akışları

**Domain Service**'ler, belirli bir **Aggregate**'e ait olmayan ancak **Domain Layer** için önemli olan iş süreçlerini veya hesaplamaları gerçekleştirir. Genellikle **stateless** (durumsuz) olurlar ve **Aggregate**'ler arasında köprü görevi görebilirler.

* **Cross-Aggregate Interaction** (Çoklu Aggregate Etkileşimi): Bir işlem birden fazla **Aggregate**'i ilgilendiriyorsa (örneğin, bir kullanıcının bakiyesinden düşüp bir siparişin ödemesini onaylama), bu tür bir mantık **Domain Service**'te yer alabilir.
    
* **Calculations and Policies** (Hesaplamalar ve Politikalar): Karmaşık hesaplamalar veya birden fazla **Aggregate**'i etkileyen iş politikaları burada konumlandırılabilir.
    

**Örnek (.NET):**

```csharp
public class PricingService
{
    public decimal CalculateDiscount(Order order)
    {
        // İndirim hesaplama gibi domain kuralı, doğrudan Order'ın içinde olmak zorunda değildir.
        if (order.TotalAmount > 1000)
            return order.TotalAmount * 0.1m;
        return 0;
    }
}
```

`PricingService`, belirli bir `Order` nesnesine bağlı olmayan, genel bir indirim hesaplama mantığını barındırır. Bu mantık, farklı siparişler veya senaryolar için tekrar kullanılabilir.

#### 🔸 **Application Service** (Uygulama Hizmeti): **Application Layer**'daki (Uygulama Katmanı) Orkestrasyon

**Application Service**'ler, **Domain Service**'lerden farklı olarak doğrudan **Application Layer**'da yer alır. Kullanıcı isteklerini (**Command**'ları) alır, uygun **Aggregate**'leri veya **Domain Service**'leri çağırır, **Repository**'ler (Depo) aracılığıyla veritabanı işlemlerini koordine eder ve sonucu geri döndürür. Genellikle **CQRS (Command Query Responsibility Segregation)** deseninde **Command Handler** veya **Query Handler** olarak görev yaparlar.

* **Orchestration Role** (Orkestrasyon Rolü): Uygulamanın dış dünyayla etkileşimini ve iç **Domain Model**'ini bir araya getiren bir orkestratör görevi görür. Doğrudan iş mantığı içermekten ziyade, mevcut **Domain Object**'lerini ve **Service**'leri kullanarak bir akışı yönetir.
    
* **Repository Interaction** (Depo Etkileşimi): Veri erişim katmanıyla (**Repository**'ler) iletişim kurarak **Aggregate**'leri yükler ve kaydeder.
    

**Örnek (.NET):**

```csharp
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand>
{
    private readonly IOrderRepository _orderRepository; // Repository bağımlılığı

    public CreateOrderHandler(IOrderRepository orderRepository)
    {
        _orderRepository = orderRepository;
    }

    public async Task Handle(CreateOrderCommand command, CancellationToken cancellationToken)
    {
        // Application Service, Aggregate'i oluşturur ve onun metodlarını çağırır.
        var shippingAddress = new Address(command.Street, command.City, command.ZipCode);
        var order = new Order(shippingAddress); 

        foreach (var item in command.Items)
        {
            order.AddItem(item.ProductId, item.ProductName, item.Price, item.Quantity);
        }

        // Aggregate'i repository aracılığıyla kalıcı hale getirir.
        await _orderRepository.AddAsync(order); 
    }
}
```

`CreateOrderHandler` bir **Application Service**'tir. Kullanıcının `CreateOrderCommand`'ını alır, `Order` **Aggregate**'ini oluşturur ve içindeki `AddItem` metodunu çağırır. Son olarak, `Order` **Aggregate**'ini bir **Repository** aracılığıyla veritabanına kaydeder. Burada işin **nasıl yapılacağı** (orkestrasyon) yönetilir.

---

### 🎯 **Aggregate** ve **Service** Arasındaki Temel Farklar

| Özellik | **Aggregate** (Varlık Topluluğu) | **Service** (Hizmet) |
| --- | --- | --- |
| **What Is It?** (Nedir?) | **Domain Model** içindeki **Data + Behavior** birleşimi | **Business Logic Carrier** (İş Mantığı Taşıyıcısı) (Orkestrasyon veya çapraz kural) |
| **State?** (Durum?) | Evet, kendi durumunu (verilerini) yönetir. | Genellikle **hayır**, **stateless**'tir (durumsuz). |
| **Where Does It Operate?** (Nerede Çalışır?) | **Domain** içinde, belirli bir varlığa odaklıdır. | **Domain Layer**'da (**Domain Service**) veya **Application Layer**'da (**Application Service**) |
| **When Is It Used?** (Ne Zaman Kullanılır?) | Nesnenin kendisiyle ilgili **Business Rules** (İş Kuralları) ve **Consistency** (Tutarlılık) sağlanacaksa. | Birden fazla **Aggregate**'i etkileyen işlemler, sistem etkileşimleri, orkestrasyon veya karmaşık hesaplamalar varsa. |

Bu ayrımı doğru yapmak, hem kodunuzun daha temiz ve anlaşılır olmasını sağlar hem de karmaşık **Domain**'lerde bakım ve genişletilebilirlik açısından büyük avantajlar sunar. Mikroservis mimarisinde, her mikroservisin kendi **Aggregate**'lerini ve bu **Aggregate**'leri yöneten **Service**'lerini barındırması, **independence (bağımsızlık)** ve **encapsulation (kapsülleme)** için kritik öneme sahiptir.

## ⚡️ **CQRS (Command Query Responsibility Segregation)** ile Entegrasyon

**Rich Domain Model**'ler ve DDD prensipleri, özellikle **CQRS (Command Query Responsibility Segregation)** mimarisiyle birlikte kullanıldığında tam potansiyelini ortaya koyar.

* **Command (Write) Side**: **Rich Domain Model**'ler ve **Aggregate**'ler, genellikle CQRS'in **Command (Write) Side**'ında kullanılır. Kullanıcıdan gelen bir **Command** (Komut) (örneğin, "Sipariş Oluştur"), ilgili **Aggregate Root**'u yükler, iş mantığını **Aggregate** üzerinde çalıştırır ve **Aggregate**'in durumunu değiştirerek veritabanına kaydeder. Tüm iş kuralları ve tutarlılık **Aggregate** içinde sağlanır.
    
* **Query (Read) Side**: Okuma işlemleri (veri sorgulama) için ise daha basit, optimize edilmiş ve genellikle **Anemic Data Model**'ler (DTO'lar - Data Transfer Objects) kullanılır. Bu taraf, doğrudan veritabanından veri okuyabilir ve karmaşık **Domain Logic** (Alan Mantığı)'na ihtiyaç duymaz. Okuma modelleri, belirli ekran veya rapor ihtiyaçlarına göre **denormalize** edilebilir. Bu sayede, okuma performansından ödün vermeden, yazma tarafındaki karmaşık **Domain Model**'i koruyabiliriz.
    

Bu ayrım, her iki tarafın bağımsız olarak ölçeklenmesine ve optimize edilmesine olanak tanır.

---

## 💬 Sıkça Sorulan Sorular (SSS)

### 1\. Her mikroserviste **Rich Domain Model** kullanmak zorunda mıyım?

Hayır. Eğer mikroservisiniz sadece basit **CRUD (Create, Read, Update, Delete)** işlemleri yapıyorsa ve karmaşık bir iş mantığı içermiyorsa, **Anemic Model** yeterli olabilir. Ancak **Business Domain**'inizde (İş Alanı) karmaşık kurallar ve davranışlar varsa, **Rich Domain Model** uzun vadede size büyük faydalar sağlayacaktır.

### 2\. **Aggregate**'ler mikroservis sınırları içinde mi kalmalı?

Evet, genellikle bir **Aggregate**, tek bir mikroservisin (veya **Bounded Context**'in) içindeki **Consistency Boundary**'yi (Tutarlılık Sınırı) temsil eder. Bir **Aggregate**'i birden fazla mikroservise yaymak, dağıtık işlem karmaşasına ve tutarlılık sorunlarına yol açabilir.

### 3\. **Application Service** ile **Domain Service** arasındaki farkı nasıl daha iyi anlarım?

* **Application Service:** "**What is being done?**" (Ne yapılıyor?) sorusuna cevap verir. Dış dünya ile **Domain** arasındaki orkestrasyonu sağlar (örneğin "Yeni Sipariş Oluştur").
    
* **Domain Service:** "**How is it done according to business rules?**" (İş kurallarına göre nasıl yapılıyor?) sorusuna cevap verir. Birden fazla **Aggregate**'i içeren veya tek bir **Aggregate**'e ait olmayan **Domain Logic**'i barındırır (örneğin "İndirim Hesapla").
    

### 4\. DDD'yi öğrenmek zor mu?

DDD, başlangıçta karmaşık görünebilir çünkü sadece teknik bir desen değil, aynı zamanda bir düşünce yapısıdır. Ancak temel prensipleri (**Bounded Context**, **Aggregate**, **Value Object** vb.) anladıkça, karmaşık iş alanlarını modelleme yeteneğiniz önemli ölçüde gelişecektir. Pratik yaparak ve küçük adımlarla başlayarak ustalaşabilirsiniz.

---

## 🔚 Sonuç

Mikroservis mimarileri, doğru yaklaşımla uygulandığında yazılım geliştirme sürecine büyük avantajlar sağlar. **Domain-Driven Design** ve **Rich Domain Model**'ler ise bu avantajları maksimuma çıkarmak için kritik bir araç setidir. İş kurallarınızı ve davranışlarınızı doğrudan **Domain Object**'lerinizin içine yerleştirmek, kodunuzu daha okunabilir, test edilebilir ve uzun vadede bakımı kolay hale getirir.

Unutmayın, "**Anemic Model**"ler basitlik vaat etse de, karmaşık **Domain**'lerde sizi hızla teknik borca ve yönetilemez bir kod tabanına sürükleyebilir. **Rich Domain Model**'ler ile, karmaşıklığı ehlileştirebilir, işinizi kodunuzda açıkça ifade edebilir ve daha sağlam, tutarlı mikroservisler inşa edebilirsiniz.

Peki, siz projelerinizde DDD prensiplerini uygularken hangi zorluklarla karşılaşıyor veya hangi faydaları görüyorsunuz? Yorumlarda düşüncelerinizi paylaşmaktan çekinmeyin!
