Aggregate pattern, consistency boundaries ve transaction management. Domain boundaries belirleme ve aggregate root tasarım prensipleri.
DDD
Aggregate
Domain
Boundaries
Consistency
Entity’leri ve Value Object’leri artık tanıyoruz. Ama gerçek dünya senaryolarında tek başına bir Entity veya Value Object çoğu zaman yeterli değildir.
Örneğin, bir Sipariş (Order) sadece tek bir nesne değil; sipariş kalemlerini, toplam tutarı, müşteri bilgilerini içerir. Bu nesnelerin hepsi birlikte bir bütün oluşturur ve bu bütün bir iş birimini temsil eder. İşte bu tür durumlarda, DDD bize Aggregate kavramını sunar.
🧱 Aggregate Nedir?
Aggregate, bir veya daha fazla Entity ve Value Object’ten oluşan, mantıksal olarak bir bütün olan domain yapısıdır.
DDD’de Aggregate’ler, iş kuralları açısından anlamlı olan birimlerdir ve dış dünyaya tek bir bütün olarak açılırlar.
📌 Ana Özellikleri:
Özellik
Açıklama
Kök (Root)
Her Aggregate’in bir Aggregate Root’u vardır (genellikle ana Entity)
Tek giriş noktası
Aggregate içindeki diğer nesnelere dışarıdan doğrudan erişilmez, Root üzerinden erişilir
Tutarlılık sınırı (Consistency Boundary)
Bir Aggregate kendi içinde tutarlı olmak zorundadır
İş kuralları burada çalışır
Tüm domain mantığı Aggregate içinde kapsüllenmelidir
🎯 Örnek: Order Aggregate
bash
public sealed class Order : Entity
{ public Order (Guid id, string orderNumber, DateTime createdDate, OrderStatusEnum status): base(id){ OrderNumber = orderNumber; CreatedDate = createdDate; Status = status; OrderLines = new List<OrderLine>();} public string OrderNumber { get; private set;} public DateTime CreatedDate { get; private set;} public OrderStatusEnum Status { get; private set;} public ICollection<OrderLine> OrderLines { get; private set;}}
Burada:
Order = Aggregate Root
OrderLine = Aggregate’in parçası ama root değil
Tüm işlemler Order üzerinden yapılır (AddItem, RemoveItem), doğrudan OrderLine’a dışarıdan müdahale edilmez.
bash
public sealed class OrderLine : Entity
{ public OrderLine(Guid id, Guid orderId, Guid productId, int quantity, Money price): base(id){ ProductId = productId; Quantity = quantity; Price = price;} public Guid OrderId { get; private set;} public Guid ProductId { get; private set;} public Product Product { get; private set;} public int Quantity { get; private set;} public Money Price { get; private set;}}
Şimdi Bizim Aggregate Root değerimiz Order olduğuna göre genel kuralımız neydi ?
Aggregate Root üzerinden içinde olan diğer nesneleri yönetmemiz gerekir.
O zaman biz bir sipariş oluşturulurken sipariş kalemlerini yani siparişte kullanılan ürünleri Order üzerinden yönetmemiz gerekiyor.
bash
public sealed class Order : Entity
{ public Order(Guid id, string orderNumber, DateTime createdDate, OrderStatusEnum status): base(id){ OrderNumber = orderNumber; CreatedDate = createdDate; Status = status; OrderLines = new List<OrderLine>();}
Yukarıda gördüğünüz gibi Sipariş oluştururken sipariş kalemlerini oluşturmak ve kaldırmak için basit bir şekilde Crate ve Remove olacak iki adet method oluşturduk.
Bu şekilde tüm yönetimleri Aggregate Root üzerinden yönetir olduk.
🧠IncreaseQuantity kısmının OrderLine içinde olması doğru mu ?
Cevap: Evet, Doğrudur — ama bağlama göre.
🎯 Neden Doğru?
OrderItem, bir ürünün sipariş üzerindeki temsilidir.
Bir OrderItem’ın sadece kendi miktarını güncellemesi, onun sorumluluğudur.
Bu, DDD’de “davranışı verinin ait olduğu yere koymak” prensibine uygundur (object-oriented encapsulation).
Yani şu doğru bir yaklaşımdır:
{
item.IncreaseQuantity(2);
}
Burada davranış OrderItem'a aittir çünkü “kaç adet sipariş edildiği” bilgisi ona özgüdür.
🛑 Ancak: Bu davranış dış dünyaya açılmamalıdır!
DDD'de Aggregate Root (yani Order) dışındaki entity'lere doğrudan dış dünyadan erişim olmamalıdır. O yüzden OrderItem'da bu metot olabilir ama sadece Order içinden çağrılmalıdır.
Yanlış:
bash
{ order.Items.First().IncreaseQuantity(1); // ❌ dış dünya doğrudan alt entity'yi değiştiriyor
}
public void AddItem(...){ var existing = _items.FirstOrDefault(...);if(existing != null
🧾 Alternatif: Order Root Tüm Mantığı Yönetebilir mi?
İstersen IncreaseQuantity metodunu OrderItem'dan alıp Order içine taşıyabilirsin. Ama bu, Order'u şişirir ve OrderItem'ın kendi sorumluluğunu kaybettirir.
DDD'nin favori tercihi: davranışı ait olduğu yerde bırakmaktır.
✅ Özetle
Soru
Cevap
IncreaseQuantityOrderItem içinde olabilir mi?
Evet, kesinlikle. Çünkü bu davranış ona aittir.
Ama dış dünyadan doğrudan çağrılır mı?
Hayır. Sadece Order içinden erişilmeli.
Başka bir yere taşımalı mıyız?
Gerek yok, davranış ait olduğu yerdedir.
🧠 Neden Aggregate?
Veri tutarlılığı sağlamak için: tüm işlemler tek bir giriş noktasından geçer.
Domain kurallarını merkezi hâle getirmek için: root üzerinden kontrol edilir.
İlişkili nesneleri bir araya toplamak için.
🔑 Özet
Aggregate, birden fazla nesnenin işsel anlamda oluşturduğu bütündür.
Ve dış dünyaya sadece Root üzerinden açılır. Böylece kurallar korunur, tutarlılık bozulmaz.
public string OrderNumber
{
get
;
private
set
;
}
public DateTime CreatedDate
{
get
;
private
set
;
}
public OrderStatusEnum Status
{
get
;
private
set
;
}
public ICollection
<
OrderLine
>
OrderLines
{
get
;
private
set
;
}
public void CreateOrderLine
(
List
<
CreateOrderDto
>
createOrder
)
{
if
(
createOrder
==
null
||
!
createOrder.Any
(
))
{
throw new ArgumentException
(
"Order oluştururken sipariş kalemi olması gerekir. ."