DDD Aggregate sınırı, ekranın gösterdiği nesnelerden veya database foreign key’lerinden çıkarılmaz. Aynı anda korunması gereken iş kurallarını ararız. Siparişin satırları ve toplamı birlikte tutarlı olmalıdır; bütün ürün stoklarını sipariş nesnesinin içine almak ise farklı bir karar ve çoğu zaman pahalı bir sınırdır.
Bir sipariş onaylandığında satır eklenememesi gerektiğini düşünelim. Bu kural sipariş aggregate’i içinde korunabilir. Buna karşılık bir üründen elde kalan miktar, farklı siparişlerin rekabet ettiği stok alanının sorumluluğudur. Sipariş nesnesinin bellekte tuttuğu stok bilgisi, başka işlemdeki güncellemeyi görmez.
DDD Aggregate sınırı için önce invariant’ı tanımlamak
Bir sipariş ekranında müşteri, adres, ürün, stok ve ödeme bilgisi birlikte gösterilebilir. Bu nesnelerin ekranda yan yana bulunması onların tek aggregate olması gerektiğini göstermez. Ekran kullanıcıya karar vermek için farklı kaynaklardan veri sunar. Aggregate ise bir değişiklik sırasında hangi kuralların birlikte korunacağını belirler. Bu iki bakış açısını karıştırırsak geniş bir object graph oluşturarak bütün ilişkileri aynı root altında toplama eğilimi oluşur. Sonuçta basit bir adres değişikliği için ürün kataloğunu ve stok kayıtlarını da yüklemek gerekebilir.
Önce kuralları açık cümlelerle yazın. Onaylanan sipariş en az bir satır içerir. Her satırın miktarı pozitiftir. Onaydan sonra satır eklenmez. Aynı ürüne ait toplam rezervasyon mevcut stoğu aşmaz. Bu dört kuralın ilk üçü siparişin kendi state’iyle değerlendirilebilir. Dördüncüsü başka siparişlerin de etkilediği stok miktarıyla ilgilidir. Siparişin içindeki geçici stok kopyası, farklı işlemin aynı anda yaptığı rezervasyonu göremez. Dolayısıyla dört kuralı aynı entity graph içinde görmek, hepsine aynı tutarlılık garantisini sağlamak anlamına gelmez.
Invariant’ın ne zaman korunması gerektiğini de belirtin. Sipariş taslak halinde eksik olabilir; ancak onaylandığı anda gerekli koşulları sağlamalıdır. Her state için aynı doğrulama kurallarını çalıştırmak, taslak kaydetme gibi iş ihtiyaçlarını engelleyebilir. Buna karşılık taslak kavramını bütün kuralları ertelemek için kullanmak da hatalıdır. Negatif miktar gibi hiçbir state’te anlamlı olmayan veri baştan reddedilebilir. “Her zaman geçerli” ile “bu state’e geçerken geçerli” kurallarını ayırmak daha doğru model kurulmasını sağlar. State geçişinin niyeti metot adında ve testte görünmelidir.
Tercihim: Aynı anda korunması gereken kuralı ifade eden en küçük aggregate sınırını kurmak. Bu seçim yükleme ve değişiklik çakışması alanını sınırlayabilir; ancak sınır dışındaki iş akışlarında koordinasyon ihtiyacı doğurabilir. Bir kural gerçekten başka entity’nin güncel state’iyle tek transaction içinde korunmak zorundaysa seçimi yeniden değerlendiririm. Yine de her referansı aggregate içine almak ilk seçenek olmamalıdır. İlişkinin anlamı, değişim sıklığı ve concurrency ihtiyacı birlikte incelenmelidir. Aggregate boyutu için bütün domain’lere uygulanabilecek sabit entity sayısı bulunmaz.
Sipariş ile ürün kataloğu neden farklı yaşam döngüleridir?
Ürün kataloğunda isim, açıklama ve güncel fiyat değişebilir. Sipariş satırı ise satın alma anında kabul edilen miktarı ve ticari bilgiyi temsil eder. Sipariş yalnızca canlı ürün entity’sine bakarsa katalog değiştiğinde geçmiş siparişin anlamı da değişebilir. Bu nedenle bazı bilgiler snapshot olarak saklanabilir. Snapshot’ın hangi alanları kapsayacağı iş ve muhasebe ihtiyacına bağlıdır. Aynı isim veya fiyatın iki yerde tutulması otomatik olarak kötü duplication değildir. İki alanın farklı zamanları ve farklı sahipleri temsil etmesi mümkündür. Veri benzerliği model sahipliğinin aynı olduğunu kanıtlamaz.
Ürün kimliği bir bağlantı sağlayabilir; fakat bağlantının yanında geçmiş anlamın korunması gerekir. Katalogda ürün pasife alınmışsa geçmiş sipariş silinmemelidir. Siparişin gösterdiği ürün adı, müşterinin satın aldığı anda gördüğü ad olabilir. Güncel katalog adı da ayrı bilgi olarak sunulabilir. Bu ayrım ekranda ve query modelinde açık olmalıdır. Domain modelinin bütün gösterim ihtiyacını navigation property ile karşılamasını beklemek yerine read projection kullanılabilir. Aggregate’in değişiklik sorumluluğu ile raporlama ihtiyacını birbirine karıştırmamak sınırı daha anlaşılır tutar.
Invariant’ı metot sınırında korumak
Aşağıdaki .NET 10 console örneğinde dışarıya değiştirilebilir bir satır listesi açılmaz. Sipariş, kendi state geçişini ve miktar kuralını yönetir. Console projesinde ek paket olmadan çalıştırılabilir. Para birimi ve fiyat politikası kapsamı daraltmak için örneğe dahil edilmemiştir.
var order = new Order();
order.AddLine("SKU-1", 2);
order.Confirm();
try
{
order.AddLine("SKU-2", 1);
throw new Exception("Expected rejection.");
}
catch (InvalidOperationException)
{
Console.WriteLine("Confirmed order rejected a new line.");
}
if (order.Lines.Count != 1)
throw new Exception("Order changed unexpectedly.");
public sealed record OrderLine(string Sku, int Quantity);
public sealed class Order
{
private readonly List<OrderLine> lines = [];
public IReadOnlyList<OrderLine> Lines => lines.AsReadOnly();
public bool Confirmed { get; private set; }
public void AddLine(string sku, int quantity)
{
if (Confirmed)
throw new InvalidOperationException(
"Order already confirmed."
);
if (string.IsNullOrWhiteSpace(sku))
throw new ArgumentException("SKU required.", nameof(sku));
if (quantity <= 0)
throw new ArgumentOutOfRangeException(nameof(quantity));
lines.Add(new OrderLine(sku, quantity));
}
public void Confirm()
{
if (Confirmed)
throw new InvalidOperationException("Already confirmed.");
if (lines.Count == 0)
throw new InvalidOperationException("Empty order.");
Confirmed = true;
}
}
Read-only view, listeye dışarıdan satır eklenmesini engeller. Satırın immutable record olması da iç alanların değiştirilmesini sınırlar. Bu davranış aynı instance’a paralel erişimi güvenli hale getirmez. Persistence katmanında concurrency token veya uygun transaction politikası gerekir.
Stok rezervasyonu ayrı bir karar
Onay sırasında stok modülüne rezervasyon isteği gönderildiğinde “sipariş kaydı başarılı, rezervasyon başarısız” durumu tasarlanmalıdır. Aynı relational transaction içinde iki değişiklik yapmak mümkün olabilir; ancak bu koordinasyon modüllerin persistence düzenini birbirine bağlar. Ayrı servis veya database sınırı varsa aynı local transaction’a güvenemeyiz.
Eventual consistency kabul edilebiliyorsa sipariş PendingReservation durumuyla başlayabilir. Rezervasyon başarılı olduğunda onaylanır, başarısız olduğunda reddedilir veya kullanıcıya alternatif sunulur. Bu süreçte timeout “kesin rezervasyon olmadı” demek değildir. İşlem kimliğiyle durumu sorgulamak ve tekrarları yönetmek gerekir.
Domain boundary yazısı, ownership kararını modül seviyesinde ele alıyor. Aggregate sınırı onun daha küçük, iş kuralı odaklı bir parçasıdır; bir bounded context ile bir aggregate aynı şey değildir.
Her ilişkiyi tek aggregate’e almak neden sorun olur?
Çok büyük aggregate, yükleme hacmini ve değişiklik çakışmalarını artırabilir. Çok küçük aggregate ise aynı anda korunması gereken kuralı application’a dağıtabilir. Tercihim, önce tutarlılık kuralını açık bir cümleyle yazmak, ardından o kuralı koruyan en küçük sınırı kurmak.
Örneğin “onaylanan siparişte en az bir satır vardır” bu sınıfın içinde korunur. “satılan toplam miktar mevcut stoğu aşmaz” ise stok tarafında atomik update veya rezervasyon mekanizması ister. Bu iki kuralı yalnızca navigation property ile birleştirmek güvence sağlamaz.
Microsoft’un domain model rehberi aggregate root’un tutarlılık sorumluluğunu açıklıyor. Sınırı çizdikten sonra testleri örnek CRUD metotlarından çok invariant ihlallerine yönlendirin.
Root üzerinden değişiklik yapmak neyi korur?
Sipariş aggregate root’u satır ekleme ve onaylama davranışını yönetir. Dışarıya mutable satır listesi açılırsa tüketici AddLine metodunu atlayıp geçersiz miktar ekleyebilir. Onay kontrolü de bypass edilmiş olur. Bu nedenle yalnızca public property setter’larını kapatmak yetmez; collection erişimi ve iç nesnelerin değişebilirliği de incelenmelidir. Örnekte private liste ve read-only view kullanılır. Satır tipi immutable record olduğundan dışarıdaki tüketici mevcut satırın miktarını değiştiremez. Bu düzen çağrı yollarını daraltır; persistence veya reflection gibi farklı erişim yollarının davranışı ayrıca değerlendirilir.
Immutable satır, listeye aynı instance üzerinden paralel ekleme sorununu çözmez. Aggregate genellikle bir iş işlemi içinde yüklenip değiştirilir. Farklı request’ler aynı siparişin ayrı instance’larını yükleyebilir. İki instance kendi içinde geçerli görünürken database’de biri diğerinin değişikliğini ezebilir. Bu nedenle in-memory invariant ile persistence concurrency farklı sorumluluklardır. DDD Aggregate sınırı nesnenin anlamını düzenler; veritabanının eşzamanlı yazma davranışını otomatik olarak belirlemez. Concurrency token, transaction isolation veya başka uygun strateji provider ve iş kuralına göre seçilmelidir.
Çalışan örnekte başarılı ve reddedilen yollar
Örnek önce yeni bir sipariş oluşturuyor ve iki adet ürün içeren satır ekliyor. Confirm çalıştığında siparişte satır bulunduğu için state onaylanmış hale geliyor. Ardından yeni satır ekleme denemesi InvalidOperationException ile reddediliyor. Exception beklenen yoldan gelmezse örnek başka bir exception atarak kontrolü başarısız kılıyor. Son kontrol satır sayısının bir kaldığını doğruluyor. Böylece yalnızca reddetme mesajını değil, başarısız işlemden sonra state’in değişmediğini de sınamış oluyoruz. Bu, aggregate davranışında önemli bir doğrulama biçimidir.
AddLine miktar ve SKU’yu değiştirmeden önce kontrol eder. Bu sıralama, geçersiz giriş geldiğinde listenin değişmeden kalmasına yardımcı olur. Gerçek metot birden fazla alanı değiştiriyorsa önce doğrulama, sonra mutation yapmak gerekebilir. Alanların bir kısmını güncelleyip son adımda hata atmak nesneyi ara state’te bırakabilir. Bu davranış yalnızca database rollback ile çözülemez; aynı instance üzerinde sonraki işlemler devam edebilir. Domain metodunun başarısızlık sonrası state’i ne olacak sorusu testte açık yer almalıdır. İstisna atılmış olması state’in güvenli kaldığını kanıtlamaz.
Örnekte para birimi, fiyatlandırma ve indirim bulunmaz. Bu alanları eklemek sipariş satırını sadece daha büyük record’a çevirmekten ibaret değildir. Para birimi uyumu, fiyatın hangi anda sabitlendiği ve indirimlerin toplamı nasıl etkilediği iş kararlarıdır. Benzer şekilde duplicate SKU’ların ayrı satır mı birleşik satır mı olacağı tanımlanmalıdır. Bu makaledeki sınıf bu politikaları uygulamadığından production sipariş modeli gibi sunulmuyor. Öğretim örneğinin gösterdiği invariant’ları yeni gereksinimlerle karıştırmadan genişletmek, kodun sınırını doğru tutmayı sağlar.
Stok rezervasyonu ayrı bir tutarlılık alanıdır
Birden fazla sipariş aynı üründen stok ayırmaya çalışır. Bu yarışın sahibi stok tarafıdır. Siparişin toplamı doğru olsa bile stok yetersiz olabilir. Stok miktarını sipariş root’una property olarak koymak, başka siparişin tüketimini görünür yapmaz. Stok rezervasyon işlemi uygun concurrency ve transaction davranışını kapsamalıdır. Ayrılan miktarın siparişe bağlanması için rezervasyon kimliği gerekebilir. Bu kimlik tekrar çağrı ve iptal akışında aynı iş etkisini tanımaya yardımcı olur. Kimliğin yaşam süresi, serbest bırakma koşulu ve tekrar semantiği açık tasarlanmalıdır.
Onay isteği geldiğinde stoktan reserve edip ardından sipariş kaydetmek kolay görünür. Fakat ikinci adım başarısız olursa stok ayrılmış halde kalabilir. Siparişi önce kaydedip sonra rezervasyon yapmak da ters kısmi başarıyı üretir. Aynı relational transaction içinde iki değişikliği koordine etmek bir seçenek olabilir; bunun persistence bağımlılığı vardır. Ayrı database veya servis sınırında aynı local transaction bulunmaz. Bu yüzden aggregate çizimiyle iş akışı koordinasyonunu ayrı düşünmek gerekir. Kuralın root içinde bulunması, dış modül çağrısının atomik gerçekleştiğini göstermez.
PendingReservation gibi ara durumların anlamı
İş modeli bekleyen siparişi kabul edebiliyorsa önce sipariş kaydedilip rezervasyon sonucu daha sonra işlenebilir. Bu sırada sipariş kesin onaylanmış sayılmaz. Kullanıcıya durum gösterilir ve rezervasyon başarısızsa ret veya alternatif akış uygulanır. Ara state, teknik bir eksikliğin üzerini örtmek için değil işin kabul ettiği süreç olarak tasarlanmalıdır. Bekleyen durumun ne kadar sürebileceği, kim tarafından sonlandırılacağı ve müşteri beklentisi açık olmalıdır. Aksi halde yarım kalmış işlemler sistemde sessizce birikir ve support ekibi için belirsiz durumlar oluşur.
Timeout geldiğinde rezervasyonun kesin başarısız olduğunu söyleyemeyiz. Stok tarafı işlemi tamamlamış ancak yanıt ulaşmamış olabilir. Bu nedenle aynı rezervasyonu tekrar çalıştırma veya durum sorgulama davranışı önem kazanır. Yanıt bilinmiyor state’i iş akışında ayrıca ele alınabilir. Hata yönetimi yalnızca catch bloğuna tekrar eklemek değildir. Tekrarın hangi kimlikle ve hangi süre boyunca güvenli olduğu belirlenmelidir. Sipariş aggregate’i geçerli kalabilir; fakat dış iş akışı belirsiz olabilir. Nesne doğruluğu ile bütün sürecin tamamlanmış olması farklı garantilerdir.
Büyük aggregate’in değişiklik maliyeti
Sipariş, müşteri, stok, ödeme ve fatura aynı root altında yönetiliyorsa tek işlem çok geniş veri yükleyebilir. Ayrıca bu alanlardan birindeki değişiklik diğerlerinin concurrency alanına girebilir. Bir müşteri adresi güncellenirken fatura veya stok işleminin aynı geniş modelle yarışması istenmeyebilir. Büyük aggregate, ilişki gezmeyi kolaylaştırabilir; buna karşılık bağımsız değişim ve transaction süresi açısından maliyet yaratabilir. Bu maliyeti gerçek kullanım senaryoları üzerinde incelemek gerekir. Entity sayısına bakıp ölçülmemiş performans sonucu çıkarmak doğru değildir. Hangi işlem hangi veriyi niçin yüklemeli sorusu daha açıklayıcıdır.
Aggregate boyutunu küçültmek de tek başına çözüm değildir. Satırları siparişten tamamen bağımsız yazılabilir hale getirirseniz onay sonrası değişmeme kuralı farklı yerlere dağılabilir. Application servisinin her satır yazmasında sipariş durumunu kontrol etmesi gerekir. Paralel işlemde kontrol ile değişiklik arasındaki süre sorun yaratabilir. Birlikte korunması gereken invariant varsa sınırı gereğinden fazla küçültmek daha zor koordinasyon üretir. Tasarımın hedefi minimum nesne sayısı değildir. Hedef, gerçek iş kuralını anlaşılır ve doğru tutarlılık alanında korumaktır.
Database ilişkileri ile domain sahipliğini ayırmak
Foreign key, ilgili kaydın varlığına ilişkin database kuralı sağlayabilir. Fakat root’un hangi business değişikliği yönettiğini belirlemez. Sipariş satırlarının siparişe bağlı olması güçlü bir sahiplik işareti olabilir; ürün kataloğuna bağlı olmaları ürünün aynı aggregate içinde olması gerektiğini söylemez. Bir ilişkinin referans mı, yaşam döngüsü sahipliği mi yoksa raporlama kolaylığı mı olduğunu ayırın. EF navigation property’leri bu anlamların hepsini aynı syntax ile gösterebilir. Domain modelleme sırasında persistence aracının sunduğu ilişki kolaylığını iş sınırı gibi kabul etmeyin.
Silme davranışı bu farkı görünür hale getirir. Sipariş silindiğinde satırların silinmesi bir iş kuralı olabilir; fakat ürün silinince geçmiş siparişlerin de silinmesi uygun olmayabilir. Cascade davranışları domain yaşam döngüsüyle uyumlu incelenmelidir. Domain modelinde kimlik referansı kullanmak bu sorumluluğu database’den tamamen çıkarmaz. Constraint ve veri bütünlüğü kararı yine gerekir. Her foreign key’i kaldırmak daha iyi DDD anlamına gelmez. Saklama modelinin garantileriyle business modelin garantileri birbirini desteklemelidir. İkisini farklı tasarım kararları olarak belgelemek daha açık sonuç verir.
Bounded context ile aggregate aynı sınır değildir
Sipariş bounded context’i içinde sipariş, iade ve başka aggregate’ler bulunabilir. Context iş dilini ve model anlamını belirler; aggregate bir değişiklikte tutarlılığı koruyan daha dar sınırdır. Bir context’in bütün entity’lerini tek root altında toplamak gerekmez. Benzer şekilde her aggregate için ayrı microservice oluşturmak zorunlu değildir. Deployment sınırı, ekip ownership’i ve operasyon ihtiyaçları başka kararlardır. Bu kavramları aynı çizim üzerinde gösterirken hangi sınırın neyi temsil ettiğini belirtin. Aksi halde bir domain model kararı gereksiz network ve veri senkronizasyon maliyetine dönüşebilir.
Farklı context’ler aynı ürün kimliğini kullanabilir, ancak ürüne farklı anlamlar verir. Stok için depo miktarı, katalog için satış bilgisi, sipariş için satın alma anındaki satır önemlidir. Tek bir ortak Product entity’si üzerinden bütün anlamları taşımak bağımsız değişim alanını daraltabilir. Context’ler arasında gereken bilgiyi contract veya projection ile geçirmek daha açık olabilir. Yine de her alan için yeni model üretmek otomatik fayda sağlamaz. Ortak anlam gerçekten mevcutsa paylaşım mümkün olabilir. Karar field benzerliğinden çok business anlamı ve değişim sebebine dayanmalıdır.
Testleri invariant ihlaline yönlendirmek
Bir aggregate testinde yalnızca Confirmed alanının true olduğunu kontrol etmek sınırlıdır. Boş siparişin onaylanamaması, onaydan sonra satır eklenememesi ve geçersiz miktarın state’i değiştirmemesi gibi davranışlar daha güçlü kontrol sağlar. Aynı public metoda farklı başlangıç state’lerinden ulaşın. Beklenen exception veya result kadar işlem sonrası nesnenin durumunu da doğrulayın. Metot birden fazla mutation yapıyorsa kısmi değişim oluşmadığını kontrol etmek gerekir. Bu testler domain modelinin verdiği garantiyi sınar; database yazma davranışına dair sonuç çıkarmaz.
Persistence testinde aggregate’in yüklenip yeniden kaydedildiğinde kurallarını koruyup korumadığı ayrıca incelenebilir. Private collection mapping’i, immutable value’ların saklanması ve concurrency token davranışı burada önem kazanır. İki ayrı context aynı siparişi değiştirirken ne olduğu gerçek provider üzerinden sınanmalıdır. In-memory nesne testi bunu göstermez. Microsoft’un domain validation rehberi invariant sorumluluğunu açıklayan temel kaynaklardan biridir. Bu makalenin örneği ise persistence içermez; concurrency garantisi uygulanmış değildir.
Domain event eklemek sınırı kendiliğinden çözmez
Sipariş onaylandığında bir domain event oluşturmak diğer işlemleri bilgilendirmek için anlamlı olabilir. Ancak event’in bellekte oluşması broker’a güvenli şekilde gönderildiğini göstermez. Commit başarısız olursa event’in nasıl ele alınacağı, commit sonrası process kapanırsa ne olacağı belirlenmelidir. Dış tüketiciye aktarım için outbox gibi mekanizma gerekebilir. Aynı event tekrar işlendiğinde tüketicinin davranışı da ayrı tasarım alanıdır. Aggregate’in bir olayı ifade etmesi ile distributed messaging’in teslim ve tekrar garantileri farklıdır. Domain modelini güçlü tutmak operasyon sorumluluğunu ortadan kaldırmaz.
Event payload’ında bütün aggregate’i taşımak da dikkat ister. İç model değişince tüketici contract’ı etkilenebilir. Ayrıca gereksiz veya hassas alanlar dışarı çıkabilir. Tüketicinin ihtiyacı olan anlamı ve kimlikleri seçmek daha kontrollü olabilir. Event adı teknik state değişikliğinden çok iş sonucunu anlatmalıdır. Fakat böyle bir isimlendirme tek başına doğru zamanlama veya atomik commit sağlamaz. Hangi olayın hangi koşulda üretildiğini ve tekrarın ne anlama geldiğini testle destekleyin. Makalede event implementasyonu bulunmadığından bu alanı ilerideki tasarım kararı olarak ele alıyoruz.
Mevcut modele geçişi küçük bir iş akışıyla yapmak
Geçiş sırasında kullanılan database kayıtlarının eski kurallara aykırı olup olmadığını da kontrol edin. Yeni constructor veya metot kısıtı, geçmişte kaydedilmiş geçersiz state’i kendiliğinden düzeltmez. Veri düzeltme gerekiyorsa hangi kayıtların etkilendiğini, düzeltmenin business anlamını ve geri dönüş ihtiyacını ayrı planlayın. Domain modelini güçlendirmek ile geçmiş veriyi yeniden yorumlamak farklı işlemlerdir. Özellikle onaylanmış siparişleri sessizce değiştirmek yerine uygun operasyon ve denetim sürecini belirlemek gerekir.
Aggregate’in yeni davranışı host dışında job veya import işleminden de çağrılıyorsa o yolları inceleyin. Sadece HTTP endpoint’in root metodunu kullanması bütün mutation yollarının korunduğunu göstermez. Kurala ulaşan her yazma yolu aynı business anlamını taşımalı; istisnalar varsa açıkça tanımlanmalıdır.
Başlangıçta public setter’larla yönetilen sipariş modeli varsa bütün property’leri bir anda private yapmak tüketicileri bozabilir. Önce kritik bir invariant seçin. Örneğin onaydan sonra satır eklemeyi yasaklayan davranışı metot üzerinden kurun. Doğrudan liste mutation’larını bulun ve bu yola taşıyın. Ardından başarısız işlem sonrası state testlerini ekleyin. Persistence mapping’i veya serialization etkisi varsa ayrıca doğrulayın. Refactoring ile yeni business politika aynı değişiklikte yapılırsa davranış farkının kaynağı anlaşılmaz hale gelebilir. Sınır düzenlemesi mevcut kuralı açık hale getirmelidir.
Sonraki adım, root’un başka modellerden hangi veriyi gerçekten bilmesi gerektiğini değerlendirmektir. Canlı stok nesnesi yerine rezervasyon sonucu veya kimliği yeterli olabilir. Katalogdan gereken satış bilgisinin hangi anda siparişe alınacağı tanımlanabilir. Her ayrımda yeni koordinasyon ihtiyacı çıkıp çıkmadığını inceleyin. DDD Aggregate sınırı çizmek, nesneleri kutulara ayırma işi değildir; işin tutarlılık sözleşmesini açıklamaktır. Bu sözleşme açık olduğunda code review, test ve operation kararları aynı anlam etrafında birleşir. Yeni kural geldiğinde de hangi sınırın neden değiştiği anlaşılabilir.
