İş mantığını hizmet katmanına taşımadan etki alanı nesne özniteliklerindeki benzersiz çelişkileri kontrol etmenin zarif bir yolu var mı?


10

Yaklaşık 8 yıldır alan güdümlü tasarımı uyarlıyorum ve bunca yıldan sonra bile, beni rahatsız eden bir şey var. Bu, bir etki alanı nesnesine karşı veri depolamada benzersiz bir kayıt olup olmadığını kontrol etmektir.

Eylül 2013'te Martin Fowler , mümkünse, tüm alan nesnelerine uygulanması gereken TellDon'tAsk prensibinden bahsetti , bu da bir mesaj döndürmeli, işlemin nasıl gittiğini (nesne yönelimli tasarımda, bu genellikle istisnalar yoluyla yapılır, işlem başarısız oldu).

Projelerim genellikle iki bölüme ayrılır; bunlardan ikisi Alan adıdır (iş kuralları içerir ve hiçbir şey içermez, alan adı tamamen kalıcılık-cahildir) ve Hizmetler'dir. CRUD verilerine kullanılan havuz katmanını bilen hizmetler.

Bir nesneye ait bir özniteliğin benzersizliği bir etki alanı / iş kuralı olduğundan, etki alanı modülüne uzun olmalıdır, bu nedenle kural tam olarak olması gerektiği yerdir.

Bir kaydın benzersizliğini kontrol edebilmek için, diyelim ki başka bir kaydın Namezaten mevcut olup olmadığını öğrenmek için geçerli veri kümesini, genellikle bir veritabanını sorgulamanız gerekir .

Etki alanı katmanı kalıcılık bilgisizdir ve verilerin nasıl alınacağına dair bir fikri yoktur, ancak yalnızca üzerlerinde işlemlerin nasıl yapılacağını, havuzların kendisine gerçekten dokunamaz.

Daha sonra uyarladığım tasarım şöyle:

class ProductRepository
{
    // throws Repository.RecordNotFoundException
    public Product GetBySKU(string sku);
}

class ProductCrudService
{
    private ProductRepository pr;

    public ProductCrudService(ProductRepository repository)
    {
        pr = repository;
    }

    public void SaveProduct(Domain.Product product)
    {
        try {
            pr.GetBySKU(product.SKU);

            throw Service.ProductWithSKUAlreadyExistsException("msg");
        } catch (Repository.RecordNotFoundException e) {
            // suppress/log exception
        }

        pr.MarkFresh(product);
        pr.ProcessChanges();
    }
}

Bu, etki alanı katmanının kendisi yerine etki alanı kurallarını tanımlayan hizmetlere sahip olmanıza ve kuralların kodunuzun birden çok bölümüne dağılmasına neden olur.

TellDon'tAsk ilkesinden bahsettim, çünkü açıkça görebileceğiniz gibi, hizmet bir eylem sunuyor (ya Productbir istisna kaydediyor ya da bir istisna atıyor), ancak yöntemin içinde, prosedürel yaklaşımı kullanarak nesneler üzerinde işlem yapıyorsunuz.

Açık çözüm, fırlatma yöntemine Domain.ProductCollectionsahip bir sınıf oluşturmaktır , ancak performansta çok eksiktir, çünkü bir Ürünün zaten aynı SKU'ya sahip olup olmadığını kodda bulmak için tüm Ürünleri veri deposundan almanız gerekir. eklemeye çalıştığınız Ürün olarak.Add(Domain.Product)ProductWithSKUAlreadyExistsException

Bu sorunu nasıl çözüyorsunuz? Bu gerçekten kendi başına bir sorun değil, hizmet katmanı yıllardır belirli etki alanı kurallarını temsil ettim. Hizmet katmanı genellikle daha karmaşık alan adı işlemlerine de hizmet eder, sadece kariyeriniz sırasında daha iyi, daha merkezi bir çözümle karşılaşıp karşılaşmadığınızı merak ediyorum.


Neden sadece bir tane sorabilir ve mantığı bulunup bulunmadığına dayandırabildiğinizde neden bir ad listesi çekelim? Kalıcı katmana ne yapacağınızı söylüyorsunuz ve arayüzle bir anlaşma olduğu sürece nasıl yapılacağını söylemiyorsunuz.
JeffO

1
Hizmet terimini kullanırken, etki alanı katmanındaki etki alanı hizmetleri ile uygulama katmanındaki uygulama hizmetleri arasındaki fark gibi daha açık bir şekilde anlaşılmalıdır. Bkz. Gorodinski.com/blog/2012/04/14/…
Erik Eidt

Mükemmel makale, @ErikEidt, teşekkür ederim. Sanırım tasarımım yanlış değil, eğer Bay Gorodinski'ye güvenebilirsem, hemen hemen aynı şeyi söylüyor: Daha iyi bir çözüm, bir kurumun gerekli bilgileri alması, yürütme ortamını etkin bir şekilde kurması ve varlığa o ve çoğunlukla SRP kırarak, benim kadar doğrudan Alan modeline depo enjekte karşı aynı objectsion sahiptir.
Andy

"Söyle, sorma" referansından bir alıntı: "Ama şahsen ben tell-dont-ask kullanmıyorum."
radarbob

Yanıtlar:


7

Etki alanı katmanı kalıcılık bilgisizdir ve verilerin nasıl alınacağına dair bir fikri yoktur, ancak yalnızca üzerlerinde işlemlerin nasıl yapılacağını, havuzların kendisine gerçekten dokunamaz.

Bu bölüme katılmıyorum. Özellikle son cümle.

Etki alanının kalıcılık cahil olması gerektiği doğru olsa da, "Etki alanı varlıklarının toplanması" olduğunu bilir. Ve bu koleksiyonu bir bütün olarak ilgilendiren etki alanı kuralları olduğunu. Teklik onlardan biri. Ve gerçek mantığın uygulanması büyük ölçüde belirli kalıcılık moduna bağlı olduğundan, etki alanında bu mantığa olan ihtiyacı belirten bir tür soyutlama olmalıdır.

Bu nedenle, adın zaten olup olmadığını sorgulayabilen, daha sonra veri deponuzda uygulanan ve adın benzersiz olup olmadığını bilmesi gereken herkes tarafından çağrılan bir arayüz oluşturmak kadar basittir.

Depoların DOMAIN hizmetleri olduğunu vurgulamak isterim. Onlar kalıcılığın soyutlarıdır. Alandan ayrı olması gereken deponun uygulanmasıdır. Bir etki alanı hizmetini çağıran etki alanı varlığında kesinlikle yanlış bir şey yoktur. Bir varlığın başka bir varlığı almak ya da bellekte kolayca saklanamayan bazı belirli bilgileri almak için havuzu kullanabilmesiyle ilgili yanlış bir şey yoktur. Havuz'un Evans'ın kitabındaki kilit kavram olmasının nedeni budur .


Giriş için teşekkürler. Depo Domain.ProductCollection, Etki Alanı katmanından nesne almaktan sorumlu olduklarını düşünerek, aklımda olanı temsil edebilir mi?
Andy

@DavidPacker Ne demek istediğini gerçekten anlamıyorum. Çünkü tüm öğeleri hafızada tutmaya gerek yok. "DoesNameExist" yönteminin uygulanması (büyük olasılıkla) veri deposu tarafında SQL sorgusu olmalıdır.
Euphoric

Ne demek olduğunu ben onları almak gerekmez tüm Ürün bilmek istediğinizde, bunun yerine bellekte bir koleksiyon veri depolayabilen, olup Domain.ProductCollectionolmadığını, ancak soran ile aynı yerine depoyu sormak Domain.ProductCollectionile bir Ürünü içerir SKU'yu geçti, bu kez, daha önce, önceden yüklenmiş ürünler üzerinde yineleme yapmak yerine altta yatan veritabanını sorgulayan depoyu soruyor (bu aslında örnek). Bunu yapmak zorunda olmadıkça tüm Ürünü hafızada saklamak istemiyorum, bunu yapmak tam bir saçmalık olurdu.
Andy

Hangisi başka bir soruya yol açar, depo daha sonra bir özelliğin benzersiz olup olmayacağını bilmeli miydi? Depoları her zaman oldukça aptalca bileşenler olarak uyguladım, onları kaydetmek için geçtiklerinizi kaydedip geçirilen koşullara göre veri almaya ve kararı hizmetlere sokmaya çalıştım.
Andy

@DavidPacker Peki, "alan bilgisi" arayüzde. Arabirim "etki alanının bu işlevselliğe ihtiyacı var" der ve veri deposu da bu işlevi sağlar. IProductRepositoryArayüzünüz varsa DoesNameExist, yöntemin ne yapması gerektiği açıktır. Ancak, Ürünün mevcut ürünle aynı ada sahip olamayacağından emin olmak, bir yerde etki alanında olmalıdır.
Euphoric

4

Set doğrulamasında Greg Young'ı okumalısınız .

Kısa cevap: fareler yuvasından çok aşağı inmeden önce, gereksinimin değerini iş perspektifinden anladığınızdan emin olmanız gerekir. Çoğaltmayı önlemek yerine tespit etmek ve hafifletmek ne kadar pahalıdır?

“Teklik” gereklilikleriyle ilgili sorun, insanların onları istemelerinin altında yatan daha derin bir neden olduğudur - Yves Reynhout

Daha uzun cevap: Bir olasılıklar menüsü gördüm, ama hepsinin ödünleşimi var.

Komutu etki alanına göndermeden önce kopyaları kontrol edebilirsiniz. Bu, istemcide veya hizmette yapılabilir (örneğiniz tekniği gösterir). Etki alanı katmanından dışarı sızan mantıktan memnun değilseniz, a ile aynı sonucu elde edebilirsiniz DomainService.

class Product {
    void register(SKU sku, DuplicationService skuLookup) {
        if (skuLookup.isKnownSku(sku) {
            throw ProductWithSKUAlreadyExistsException(...)
        }
        ...
    }
}

Tabii ki, bu şekilde DeduplicationService uygulamasının mevcut skusun nasıl aranacağı hakkında bir şeyler bilmesi gerekecektir. Bu yüzden, işin bir kısmını etki alanına geri iterken, yine de aynı temel sorunlarla karşı karşıyasınız (set validasyonu için bir cevaba ihtiyacınız var, yarış koşulları ile ilgili problemler).

Doğrulamayı kalıcılık katmanınızın kendisinde yapabilirsiniz. İlişkisel veritabanları ayarlanmış doğrulamada gerçekten iyidir . Ürününüzün sku sütununa benzersiz bir kısıtlama koyun ve hazırsınız. Uygulama sadece ürünü depoya kaydeder ve bir sorun varsa köpüren bir kısıtlama ihlali alırsınız. Bu yüzden uygulama kodu iyi görünüyor ve yarış durumunuz ortadan kaldırılıyor, ancak "alan adı" kuralları sızdırılıyor.

Alan adınızda, bilinen skus grubunu temsil eden ayrı bir toplama oluşturabilirsiniz. Burada iki varyasyon düşünebilirim.

Biri ProductCatalog gibi bir şey; ürünler başka bir yerde bulunur, ancak ürünler ve skus arasındaki ilişki sku benzersizliğini garanti eden bir katalog tarafından korunur. Değil bu, ürünlerin ima yok SKU'ları var; skus bir ProductCatalog tarafından atanır (skus'un benzersiz olması gerekiyorsa, bunu yalnızca tek bir ProductCatalog toplamına sahip olarak elde edersiniz). Alan adı uzmanlarınızla her yerde bulunan dili inceleyin - böyle bir şey varsa, bu doğru yaklaşım olabilir.

Alternatif bir sku rezervasyon hizmeti gibi bir şeydir. Temel mekanizma aynıdır: bir agrega tüm skusları bilir, bu nedenle kopyaların girişini önleyebilir. Ancak mekanizma biraz farklıdır: bir ürüne atamadan önce bir sku üzerinde bir kiralama elde edersiniz; Ürünü oluştururken, kirayı sku'ya geçirirsiniz. Halen oyunda bir yarış durumu var (farklı agregalar, bu nedenle farklı işlemler), ancak farklı bir lezzeti var. Buradaki gerçek dezavantajı, alan adı modeline, alan adı dilinde gerçekten bir gerekçe olmadan bir kiralama hizmeti yansıtmanızdır.

Tüm ürün varlıklarını tek bir toplamda, yani yukarıda açıklanan ürün kataloğuna çekebilirsiniz. Bunu yaparken kesinlikle skus'un benzersizliğini elde edersiniz, ancak maliyet ek bir çekişmedir, herhangi bir ürünü değiştirmek gerçekten tüm kataloğu değiştirmek anlamına gelir.

İşlemi bellekte yapmak için tüm ürün SKU'larını veritabanından dışarı çekme ihtiyacını sevmiyorum.

Belki de gerek yok. Sku'nuzu bir Bloom filtresiyle test ederseniz , seti yüklemeden birçok benzersiz skus keşfedebilirsiniz.

Kullanım durumunuz hangi skus'u reddettiğiniz konusunda keyfi olmanıza izin veriyorsa, tüm yanlış pozitifleri ortadan kaldırabilirsiniz (istemcilere komutu göndermeden önce önerdikleri skusu test etmesine izin verirseniz büyük bir sorun değil). Bu, seti belleğe yüklemekten kaçınmanıza izin verir.

(Daha kabul edilebilir olmak istiyorsanız, çiçek filtresinde bir maç olması durumunda skus'u tembel olarak yüklemeyi düşünebilirsiniz; yine de bazen tüm skusları belleğe yükleme riski vardır, ancak izin verirseniz genel durum olmamalıdır komut göndermeden önce hata olup olmadığını kontrol etmek için istemci kodu).


İşlemi bellek içinde yapmak için tüm ürün SKU'larını veritabanından dışarı çekme ihtiyacını sevmiyorum. İlk soruda önerdiğim ve performansını sorguladığım bariz bir çözüm. Ayrıca, benzersizliğinden sorumlu olmak için DB'nizdeki kısıtlamaya güvenen IMO kötüdür. Yeni bir veritabanı motoruna geçecekseniz ve bir şekilde benzersiz kısıtlama bir dönüşüm sırasında kaybolduysa, kodu kırdınız, çünkü daha önce var olan veritabanı bilgileri artık orada değil. Neyse bağlantı için teşekkür ederim, ilginç okuma.
Andy

Belki bir Bloom filtresi (düzenlemeye bakınız).
VoiceOfUnreason
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.