DDD, karmaşıklığı yüksek yazılımlar için ortaya atılmış bir yaklaşımdır. Bu yaklaşım, yazılımların geliştirilmesi ve yazılımların sürekliliğinin sağlanması aşamalarındaki karmaşıklıkları basitleştirir. DDD, Strategical ve Tactical olmak üzere ikiye ayrılır. DDD'nin felsefesi de özü de strategical pattern kısmındadır. Fakat bu yazının konusu strategical pattern olmayacak. Tactical pattern, mimari kısım ile ilgilenir. Sınıfların, metotların ve bağımlılıkların nasıl olması gerektiği ile ilgilidir. Yani clean architecture diyebiliriz. O zaman başlayalım.
Value Objects
Her türlü özellik için framework'ün bize sunduğu ilkel tipleri tercih ediyoruz. Buna Primitive Obsession deniyor. Örneğin; telefon numarası için string'i tercih edebiliriz. .NET'de string için 10'dan fazla metot var. ToLower, PadLeft vs. Fakat bu metotlardan hangisi bir telefon numarası için bir şey ifade eder? String kullanmak yerine PhoneNumber adında bir sınıf oluşturabiliriz.
Bir value object kendisi ile ilgili business'lara da sahip olabilir. Örneğin; 2FA için telefon numarasının son dört hanesini gösteririz. Bir string üzerinden bu dört haneyi almak için ayrı class ve metot yazabiliriz. Fakat bu şekilde uygulamamız gittikçe kimliksizleşmeye başlar. Bir klasör altına birbirleri ile hiç ilgisi olmayan helper class'lar eklenmeye başlar. Helper class yerine PhoneNumber sınıfına metot ekleyebiliriz.
Bir value object kendisi ile ilgili kuralları, validation'ları içerebilir. Örneğin telefon numarasının doğru formatta girilip girilmediğini nesnenin constructor'ında kontrol edebiliriz.
Value object, kimliği (id'si) olmayan nesnelerdir. Tüm özellikleri aynı olan iki value object birbirine eşittir.
Bir value object oluşturulduktan sonra state'i değiştirilemez. Yani immutable'dır. Değiştirmek yerine yeniden bir nesne oluşturmamız gerekir.
Bir value object kendi başına yaşayamaz ve bir entity'e ait olmak zorundadır. Örneğin telefon numarası tek başına bir şey ifade etmez ve bir müşteriye aittir.
Bir value object birbiri ile ilişkili birden fazla özelliği gruplamak için de kullanılabilir. Örneğin; para. Para, miktardan (örneğin; 100) ve birimden (örneğin; ₺) oluşur. Bunu value object olarak tasarlamazsak Amount ve Currency özelliği için iki farklı property eklememiz gerekir. Yukarıda bahsettiğim Primitive Obsession durumu ortaya çıkar. Ayrıca parayı formatlamak için yukarıda bahsettiğim gibi helper class'lar yazmak gerekebilir. Bunun doğru bir yol olmadığını daha önce belirtmiştim.
Entities
Entity, kimliği olan (id'si) olan nesnelerdir. Kimliği aynı olan iki entity birbirine eşittir. Value Object'te ise tüm özellikleri aynı olan iki value object eşittir.
Entity'nin oluşturulması için zorunlu olan minimum bilgiler constructor'da alınır. Detay bilgiler farklı metotlar ile alınabilir. Genellikle mutable'dır. ValueObject'in aksine bir entity'nin özellikleri sonrasında değiştirilebilir.
Aggregates & Aggregate Roots
Aggregate de kimliği olan (id'si) olan nesnelerdir. Yani aynı zamanda bir entity'dir. Tek bir transaction içerisinde olması gereken Entity'lerin ve Value Object'lerin kümesidir. Dolayısıyla entity'lerin ve value object'lerin tutarlılığından sorumludur. Aggregate kendisi ile ilgisi olmayan bir entity'yi veya aggregate'i navigation property olarak barındıramaz. Id'lerini referans olarak barındırır.
Domain Services
Domain kurallarını uygulayan servislerdir. Bloklu bir banka müşterisinin hesabından para çekememesi bir domain kuralıdır. Bu kural kesinlikle domain katmanında olmalıdır. Yani Account üzerinden Withdrawal metodu çağrıldığında bu kural işletilmelidir. Fakat Customer ve Account farklı aggregate'lerdir. Account, Customer'ı navigation property olarak barındıramaz. O zaman bu kuralı nasıl işleteceğiz? Withdrawal metoduna CustomerRepository geçirebilir ve kontrollerimizi yapabiliriz.
Buna Impure Domain Model denir. Çünkü bir domain modeli veritabanından sorgu çekemez. Sadece diğer domain modelleri ile etkileşime geçebilir. O zaman Withdrawal metoduna Customer'ı geçirebiliriz.
Pure Domain Model haline geldi. Fakat domain kuralları her zaman bu kadar basit olmaz. Çok daha kompleks olabilir, çok daha fazla domain modelleri ile iletişime geçebilir. O zaman ne yapacağız? Bu tür durumlarda kuralları ayrı bir sınıf üzerinde işletebiliriz.
Bu yazıda genel hatlarını çizdiğimiz konuların detaylı örneklerini video'dan izleyebilir ve örnek projeye Github adresimden ulaşabilirsiniz.
Vesselam.
Yorumlar