DDD - Varlıkların Depolara doğrudan erişememesi kuralı


185

Etki Alanına Dayalı Tasarım'da, Kuruluşların doğrudan Depolara erişmemesi gerektiği konusunda çok fazla anlaşma olduğu görülmektedir.

Bu Eric Evans Domain Driven Design kitabından mı geldi yoksa başka bir yerden mi geldi?

Bunun arkasındaki akıl yürütme için bazı iyi açıklamalar nelerdir?

edit: Açıklığa kavuşturmak için: Veri erişimini iş mantığından ayrı bir katmana ayırmanın klasik OO uygulaması hakkında konuşmuyorum - DDD'de, Varlıkların verilerle konuşması gerekmeyen belirli düzenleme hakkında konuşuyorum erişim katmanına hiç (yani Havuz nesnelerine referans tutmaları gerekmez)

güncelleme: Ödülünü BacceSR'a verdim çünkü cevabı en yakın görünüyordu, ama hala bu konuda karanlıktayım. Eğer bu kadar önemli bir prensipse, bir yerde çevrimiçi olarak bazı iyi makaleler olmalı, elbette?

Güncelleme: Mart 2013, sorudaki artışlar buna çok ilgi duyduğunu gösteriyor ve çok sayıda cevap olmasına rağmen, insanların bu konuda fikirleri varsa daha fazlasına yer olduğunu düşünüyorum.


Soruma bir göz atın stackoverflow.com/q/8269784/235715 , Varlık veri havuzuna erişmeden mantığı yakalamak zor olduğunda bir durum gösterir. Bence varlıkların depolara erişimi olmamalı ve kodun depo referansı olmadan yeniden yazılabileceği durumumda bir çözüm var, ancak şu anda herhangi bir şey düşünemiyorum.
Alex Burtsev

Nereden geldiğini bilmiyorum. Düşüncelerim: Bu yanlış anlaşılmanın DDD'nin neyle ilgili olduğunu anlamayan insanlardan geldiğini düşünüyorum. Bu yaklaşım, yazılımın uygulanması için değil, bunun için tasarlanmıştır (etki alanı .. tasarım). O zamanlar mimarlarımız ve uygulayıcılarımız vardı, ama şimdi sadece yazılım geliştiricileri var. DDD mimarlar içindir. Bir mimar yazılım tasarlarken, hazırlanan tasarımı uygulayacak geliştiriciler için bir belleği veya veritabanını temsil etmek için bir araç veya desene ihtiyacı vardır. Ancak tasarımın kendisi (işletme açısından) bir depoya sahip değildir veya buna ihtiyaç duymaz.
berhalak

Yanıtlar:


47

Burada biraz karışıklık var. Depolar toplu köklere erişir. Agrega kökleri varlıklardır. Bunun nedeni, endişelerin ayrılması ve iyi katmanlaşmadır. Bu, küçük projeler için bir anlam ifade etmez, ancak "Ürün Deposu aracılığıyla bir ürüne erişirsiniz. Ürün, ProductCatalog nesnesi de dahil olmak üzere bir dizi varlık için toplu bir köktür. ProductCatalog'u güncellemek istiyorsanız, ProductRepository'den geçmelisiniz. "

Bu şekilde iş mantığı ve işlerin nerede güncellendiği konusunda çok, çok açık bir şekilde ayrılırsınız. Kendi başına kapalı olan ve tüm bu karmaşık şeyleri yapan tüm programı ürün kataloğuna yazan bir çocuğunuz yok ve bunu yukarı akış projesine entegre etmek söz konusu olduğunda, orada oturup ona bakıyorsunuz her şey terk edilmelidir. Aynı zamanda insanlar takıma katıldıklarında, yeni özellikler eklediklerinde, nereye gideceklerini ve programı nasıl yapılandıracaklarını bilirler.

Fakat bekle! Depo, Depo Modelinde olduğu gibi kalıcı katmanı da ifade eder. Daha iyi bir dünyada, bir Eric Evans Deposu ve Havuz Kalıbı ayrı isimlere sahip olurdu, çünkü bunlar biraz üst üste gelme eğilimindedir. Havuz desenini almak için, bir servis veriyolu veya bir olay modeli sistemiyle verilere erişmenin diğer yollarıyla kontrastınız vardır. Genellikle bu seviyeye geldiğinizde, Eric Evans'ın Depo tanımı bu tarafa gider ve sınırlı bir bağlam hakkında konuşmaya başlarsınız. Sınırlı her içerik aslında kendi uygulamasıdır. Ürün kataloğuna bir şeyler eklemek için gelişmiş bir onay sisteminiz olabilir. Orijinal tasarımınızda ürün orta parça idi, ancak bu sınırlı bağlamda ürün kataloğu. Yine de bir servis veri yolu üzerinden ürün bilgilerine erişebilir ve ürünü güncelleyebilirsiniz,

Orijinal sorunuza geri dönün. Bir varlığın içinden bir depoya erişiyorsanız, varlık gerçekten bir ticari varlık değil, muhtemelen bir hizmet katmanında bulunması gereken bir şeydir. Bunun nedeni, varlıkların iş nesnesi olması ve mümkün olduğunca DSL (alana özgü dil) gibi olmaları ile ilgili olmalarıdır. Bu katmanda yalnızca işletme bilgileri var. Bir performans sorununu gideriyorsanız, yalnızca işletme bilgilerinin burada olması gerektiğinden başka bir yere bakmayı bilirsiniz. Aniden, burada uygulama sorunlarınız varsa, gerçekten DDD'nin kalbi olan bir uygulamayı genişletmeyi ve korumayı çok zorlaştırıyorsunuz: sürdürülebilir yazılım yapma.

1. Yoruma Yanıt : Doğru, güzel soru. Bu nedenle tüm doğrulama etki alanı katmanında gerçekleşmez. Sharp, istediğinizi yapan "DomainSignature" özelliğine sahiptir. Kalıcılığın farkındadır, ancak bir öznitelik olması etki alanı katmanını temiz tutar. Örneğinizde aynı adda yinelenen bir varlığın bulunmamasını sağlar.

Ancak daha karmaşık doğrulama kuralları hakkında konuşalım. Amazon.com olduğunuzu varsayalım. Hiç süresi dolmuş kredi kartıyla bir şey sipariş ettiniz mi? Kartı güncellemediğim ve bir şey satın aldığım yer var. Siparişi kabul eder ve UI bana her şeyin şeftali olduğunu bildirir. Yaklaşık 15 dakika sonra, siparişimle ilgili bir sorun olduğunu belirten bir e-posta alacağım, kredi kartım geçersiz. Burada olan, ideal olarak, etki alanı katmanında bazı normal ifade doğrulaması olmasıdır. Bu doğru bir kredi kartı numarası mı? Cevabınız evet ise, siparişe devam edin. Bununla birlikte, harici bir hizmetin kredi kartında ödeme yapıp yapamayacağını görmek için sorgulandığı uygulama görevleri katmanında ek doğrulama yapılır. Değilse, aslında hiçbir şey göndermeyin, siparişi askıya alın ve müşteriyi bekleyin.

Bu servis katmanında doğrulama nesneleri oluşturmak için korkmayın kutu erişim depoları. Sadece etki alanı katmanından uzak tutun.


15
Teşekkürler. Ancak, varlıklara (ve ilgili fabrikalarına ve özelliklerine vb.) Mümkün olduğunca fazla iş mantığı almaya çalışmalıyım, değil mi? Ancak hiçbirinin Veri Havuzu üzerinden veri almasına izin verilmiyorsa, (makul derecede karmaşık) bir iş mantığı nasıl yazmam gerekir? Örneğin: Sohbet odası kullanıcısının, adını başka biri tarafından kullanılmış olan bir adla değiştirmesine izin verilmez. Bu kuralın ChatUser varlığı tarafından oluşturulmasını istiyorum, ancak oradan depoya vuramazsanız bunu yapmak çok kolay değil. Peki ne yapmalıyım?
codeulike

Yanıtım yorum kutusunun izin verdiğinden daha büyüktü, düzenlemeye bakın.
kertosis

6
Varlığınız kendini zarardan nasıl koruyacağını bilmelidir. Bu, geçersiz bir duruma geçemediğinden emin olmayı içerir. Sohbet odası kullanıcısı ile tanımladığınız şey, varlığın kendisini geçerli tutmak zorunda olduğu mantığa ek olarak iş mantığıdır. İş mantığı, ChatUser varlığına değil, gerçekten bir Sohbet Odası hizmetine ait.
Alec

9
Teşekkürler Alec. Bunu ifade etmenin açık bir yolu. Ama bana öyle geliyor ki Evans'ın 'tüm iş mantığının etki alanı katmanına girmesi gerekiyor' 'alan odaklı altın kuralı' varlıklar depolara erişmemeli 'kuralı ile çelişiyor. Bunun nedenini anlarsam bununla yaşayabilirim, ancak kurumların neden depolara erişmemesi gerektiğine dair iyi bir açıklama bulamıyorum. Evans bundan açıkça bahsetmiyor gibi görünüyor. Nereden geldi? Bazı iyi literatüre işaret eden bir cevap gönderebilirseniz, kendinize 50pt'lik bir ödül
koyabilirsiniz:)

4
"Onun küçük anlamında bir anlamı yok" Bu takımların yaptığı büyük bir hatadır ... küçük bir projedir, böyle yapabilirim ve bu ... böyle düşünmeyi bırak. Çalıştığımız küçük projelerin çoğu, iş gereklilikleri nedeniyle büyükleşiyor. Küçük veya büyük soluk bir şey yaparsanız, doğru yapın.
MeTitus

35

İlk başta, bazı varlıklarımın depolara erişmesine (yani bir ORM olmadan tembel yükleme) izin vermeye ikna oldum. Daha sonra, yapmamam gereken ve alternatif yollar bulabileceğim sonucuna vardım:

  1. Bir istekte niyetimizi ve alan adından ne istediğimizi bilmeliyiz, bu nedenle Toplu davranış oluşturmadan veya çağırmadan önce depo çağrıları yapabiliriz. Bu aynı zamanda tutarsız bellek içi durum probleminden ve tembel yükleme ihtiyacından kaçınmaya yardımcı olur (bu makaleye bakın ). Koku, artık veri erişimi konusunda endişelenmeden varlığınızın bir bellek içi örneğini oluşturamayacağınızdır.
  2. CQS (Komut Sorgu Ayırma), kuruluşlarımızdaki şeyler için depo çağırma isteğini azaltmaya yardımcı olabilir.
  3. Etki alanı mantığı ihtiyaçlarını kapsüllemek ve iletmek için bir belirtim kullanabilir ve bunun yerine bunu depoya aktarabiliriz (bir hizmet bu şeyleri bizim için düzenleyebilir). Şartname, bu değişmezi korumakla görevli kuruluştan gelebilir. Depo, belirtimin bazı bölümlerini kendi sorgu uygulamasına yorumlayacak ve sorgu sonuçlarındaki belirtimden kurallar uygulayacaktır. Bu, etki alanı mantığını etki alanı katmanında tutmayı amaçlamaktadır. Aynı zamanda her yerde kullanılan dili ve iletişimi daha iyi hizmet eder. "Gecikmiş sipariş belirtimi" ve "yerleştirilen_at'ın sysdate tarihinden 30 dakikadan daha az olduğu tbl_order'dan filtre siparişi" demeye karşı düşünün (bu cevaba bakınız ).
  4. Tek Sorumluluk İlkesi ihlal edildiğinden, kurumların davranışları hakkında mantık yürütmeyi zorlaştırır. Depolama / kalıcılık sorunlarını çözmeniz gerekiyorsa nereye gideceğinizi ve nereye gitmeyeceğinizi biliyorsunuz.
  5. Bir işletmeye küresel duruma iki yönlü erişim verme tehlikesini önler (depo ve alan adı hizmetleri aracılığıyla). Ayrıca işlem sınırınızı da kırmak istemezsiniz.

Etki Alanına Dayalı Tasarım Uygulama kırmızı kitabında Vernon Vaughn, bildiğim iki yerde bu konuya atıfta bulunuyor (not: bu kitap, önsözde okuyabileceğiniz gibi Evans tarafından tamamen onaylanmıştır). Hizmetler ile ilgili Bölüm 7'de, bir kullanıcının kimlik doğrulaması yapıp yapmadığını belirlemek için bir topluluğun depo ve başka bir toplama kullanması ihtiyacını çözmek için bir etki alanı hizmeti ve bir belirtim kullanır. O şöyle diyor:

Genel bir kural olarak, mümkünse Agregaların içinden Depoların (12) kullanımından kaçınmaya çalışmalıyız.

Vernon, Vaughn (2013-02-06). Etki Alanına Dayalı Tasarımın Uygulanması (Kindle Location 6089). Pearson Eğitim. Kindle Sürümü.

Toplamalarla ilgili Bölüm 10'da, "Model Gezinme" başlıklı bölümde (diğer toplama köklerine başvurmak için küresel benzersiz kimliklerin kullanılmasını önerdikten hemen sonra) diyor:

Kimlik referansı, modelde gezinmeyi tamamen engellemez. Bazıları arama için bir Toplama içinden bir Depo (12) kullanacaktır. Bu teknik Bağlantısız Etki Alanı Modeli olarak adlandırılır ve aslında bir tür tembel yükleme biçimidir. Ancak önerilen farklı bir yaklaşım vardır: Toplama davranışını çağırmadan önce bağımlı nesneleri aramak için bir Depo veya Etki Alanı Hizmeti (7) kullanın. Bir istemci Uygulama Hizmeti bunu kontrol edebilir ve ardından Toplam'a gönderebilir:

Kodda bunun bir örneğini göstermeye devam ediyor:

public class ProductBacklogItemService ... { 

   ... 
   @Transactional 
   public void assignTeamMemberToTask( 
        String aTenantId, 
        String aBacklogItemId, 
        String aTaskId, 
        String aTeamMemberId) { 

        BacklogItem backlogItem = backlogItemRepository.backlogItemOfId( 
                                        new TenantId( aTenantId), 
                                        new BacklogItemId( aBacklogItemId)); 

        Team ofTeam = teamRepository.teamOfId( 
                                  backlogItem.tenantId(), 
                                  backlogItem.teamId());

        backlogItem.assignTeamMemberToTask( 
                  new TeamMemberId( aTeamMemberId), 
                  ofTeam,
                  new TaskId( aTaskId));
   } 
   ...
}     

Ayrıca, bir etki alanı hizmetinin Agrega komut yönteminde çift ​​gönderme ile birlikte nasıl kullanılabileceğine dair başka bir çözümden bahsetmeye devam ediyor . (Kitabını okumanın ne kadar yararlı olduğunu tavsiye edemem. Nihayetinde internette dolaşmaktan yorulduktan sonra, hak ettiği paranın üzerine çatalla ve kitabı okuyun.)

Sonra her zaman zarif Marco Pivetta @Ocramius ile bir tartışma vardı bana bir etki alanı bir özellik çekme ve bunu kullanarak kod biraz gösterdi:

1) Bu önerilmez:

$user->mountFriends(); // <-- has a repository call inside that loads friends? 

2) Bir alan adı hizmetinde bu iyidir:

public function mountYourFriends(MountFriendsCommand $mount) { /* see http://store.steampowered.com/app/296470/ */ 
    $user = $this->users->get($mount->userId()); 
    $friends = $this->users->findBySpecification($user->getFriendsSpecification()); 
    array_map([$user, 'mount'], $friends); 
}

1
Soru: Her zaman geçersiz veya tutarsız bir durumda nesne oluşturmamamız öğretilir. Kullanıcıları depodan yüklediğinizde ve getFriends()başka bir şey yapmadan önce aradığınızda , dosya boş veya tembel olarak yüklenir. Boşsa, bu nesne yalan söyler ve geçersiz bir durumda. Bunun hakkında bir fikrin var mı?
Jimbo

Depo, bir örneği yeni oluşturmak için Etki Alanını çağırır. Etki Alanından geçmeden bir Kullanıcı örneği alamazsınız. Bu cevabın ele aldığı problem tersidir. Etki Alanı Depoya başvuruyorsa ve bundan kaçınılmalıdır.
prograhammer

28

Bu çok iyi bir soru. Bununla ilgili biraz tartışmayı dört gözle bekleyeceğim. Ama bence bazı DDD kitaplarında ve Jimmy nilssons ve Eric Evans'da bahsedildi . Sanırım depresyon modelinin nasıl kullanılacağına dair örnekler üzerinden de görülebilir.

AMA tartışalım. Bence çok geçerli bir düşünce, neden bir işletmenin başka bir varlığa nasıl devam edeceğini bilmeli? DDD için önemli olan, her işletmenin kendi "bilgi alanını" yönetme sorumluluğuna sahip olması ve diğer varlıkları nasıl okuyacağı veya yazacağı hakkında hiçbir şey bilmemesi gerektiğidir. Muhtemelen sadece B Varlıklarını okumak için A Varlığına bir depo arabirimi ekleyebilirsiniz. Ancak risk, B'nin nasıl devam edeceğine dair bilgiyi ortaya çıkarmanızdır.

Gördüğünüz gibi A işletmesi B işletmesinin yaşam döngüsüne daha fazla dahil olabilir ve bu modele daha fazla karmaşıklık katabilir.

Sanırım (herhangi bir örnek olmadan) birim testi daha karmaşık olacak.

Ancak eminim ki, her zaman varlıklar aracılığıyla depoları kullanma eğiliminiz olan senaryolar olacaktır. Geçerli bir yargıya varmak için her senaryoya bakmalısınız. Lehte ve aleyhte olanlar. Ama bence depo varlık çözümü birçok Eksileri ile başlıyor. Eksileri dengeleyen Artıları ile çok özel bir senaryo olmalı ....


1
İyi bir nokta. Eski okul etki alanı modelinin muhtemelen B varlığının kalıcı olmasına izin vermeden kendisini doğrulamaktan sorumlu olacağını düşünüyorum. Evans'ın Depo kullanmayan Kuruluşlardan bahsettiğinden emin misiniz? Kitabın ortasındayım ve henüz bahsetmedi ...
codeulike

Birkaç yıl önce kitabı okudum (3) ... ve hafızam başarısız oluyor. Tam olarak ifade edip etmediğini hatırlayamıyorum AMA bunu örneklerle gösterdiğine inanıyorum. Ayrıca, Kargo örneğinin topluluk yorumlarını (kitabından) dddsamplenet.codeplex.com adresinde bulabilirsiniz . Kod projesini indirin (Vanilla projesine bakın - kitaptaki örneği). Havuzların yalnızca etki alanı varlıklarına erişmek için Uygulama katmanında kullanıldığını göreceksiniz.
Magnus Backeus

1
DDD SmartCA örneğini p2p.wrox.com/… kitabından indirdiğinizde , depoların hizmetlerde (burada garip bir şey değil) kullanıldığı, ancak hizmetlerin kurum içinde kullanıldığı başka bir yaklaşım (bu bir RIA windows istemcisi olsa da) göreceksiniz. Bu bir şey yapmazdım AMA ben bir webb uygulaması adamım. Çevrimdışı çalışabilmeniz gereken SmartCA uygulaması senaryosu düşünüldüğünde, ddd tasarımı farklı görünecektir.
Magnus Backeus

SmartCA örneği kulağa ilginç geliyor, hangi bölümde? (kod indirmeleri bölüme göre düzenlenmiştir)
codeulike

1
@codeulike Şu anda ddd kavramlarını kullanarak bir çerçeve tasarlıyorum ve uyguluyorum. Bazen doğrulama işleminin veritabanına erişmesi ve onu sorgulaması gerekir (örnek: çoklu sütun benzersiz dizin denetimi için sorgulama). Buna ve sorguların depo katmanına yazılması gerektiğine göre, etki alanı varlıklarının referanslara sahip olması gerekir Doğrulamayı tamamen etki alanı modeli katmanına koymak için etki alanı modeli katmanındaki depo arabirimleri. Etki alanı varlıklarının depolara erişimi olması nihayet sorun değil mi?
Karamafrooz

13

Veri erişimini neden ayırmalı?

Kitaptan, Model Odaklı Tasarım bölümünün ilk iki sayfasının, etki alanı modelinin uygulanmasından teknik uygulama ayrıntılarını neden özetlemek istediğinize dair bazı gerekçeler verdiğini düşünüyorum.

  • Etki alanı modeli ile kod arasında sıkı bir bağlantı tutmak istiyorsunuz
  • Teknik kaygıları ayırmak, modelin uygulama için pratik olduğunu kanıtlamaya yardımcı olur
  • Her yerde bulunan dilin sistemin tasarımına nüfuz etmesini istiyorsunuz

Tüm bunlar sistemin gerçek uygulamasından boşanan ayrı bir "analiz modelinden" kaçınmak amacıyla görünüyor.

Kitaptan anladığım kadarıyla, bu "analiz modelinin" yazılım uygulamasını düşünmeden tasarlanabileceğini söylüyor. Geliştiriciler iş tarafı tarafından anlaşılan modeli uygulamaya çalıştıklarında, gereklilik nedeniyle kendi soyutlamalarını oluştururlar, iletişim ve anlayışta bir duvara neden olurlar.

Diğer yönde, etki alanı modeline çok fazla teknik endişe getiren geliştiriciler de bu bölünmeye neden olabilir.

Dolayısıyla, kalıcılık gibi endişelerin ayrılmasının uygulanmasının, bu tasarıma karşı farklı bir analiz modelinin korunmasına yardımcı olabileceğini düşünebilirsiniz. Kalıcılık gibi şeyleri modele sokmak gerekliyse, o zaman kırmızı bir bayraktır. Belki de model uygulama için pratik değildir.

Alıntı yapmak:

"Tek model hata olasılığını azaltır, çünkü tasarım şimdi dikkatle düşünülmüş modelin doğrudan bir büyümesidir. Tasarım ve hatta kodun kendisi bir modelin iletişim yeteneğine sahiptir."

Bunu yorumlama şeklim, eğer veritabanı erişimi gibi şeylerle ilgilenen daha fazla kod satırı ile sonuçlanırsanız, bu iletişimi kaybedersiniz.

Bir veritabanına erişme ihtiyacı benzersizliği kontrol etmek gibi bir şeyse, şunlara bir göz atın:

Udi Dahan: DDD'yi uygularken ekiplerin yaptığı en büyük hatalar

http://gojko.net/2010/06/11/udi-dahan-the-biggest-mistakes-teams-make-when-applying-ddd/

"Tüm kurallar eşit oluşturulmaz"

ve

Etki Alanı Modeli Modelini Kullanma

http://msdn.microsoft.com/en-us/magazine/ee236415.aspx#id0400119

aynı konuya değinen "Etki Alanı Modelini Kullanmama Senaryoları" altında.

Veri erişimini ayırma

Arayüz üzerinden veri yükleme

"Veri erişim katmanı", gerekli verileri almak için aradığınız bir arabirim üzerinden soyutlanmıştır:

var orderLines = OrderRepository.GetOrderLines(orderId);

foreach (var line in orderLines)
{
     total += line.Price;
}

Artıları: Arabirim "veri erişimi" tesisat kodunu ayırır ve yine de testler yazmanıza izin verir. Veri erişimi, genel bir stratejiden daha iyi performans sağlayarak duruma göre işlenebilir.

Eksileri: Arama kodu neyin yüklü ve neyin yüklü olmadığını varsaymalıdır.

Diyelim GetOrderLines performans nedenleriyle null ProductInfo özelliğine sahip OrderLine nesnelerini döndürür. Geliştiricinin arabirimin arkasındaki kod hakkında derin bilgiye sahip olması gerekir.

Bu yöntemi gerçek sistemlerde denedim. Sonuçta, performans sorunlarını düzeltmek için her zaman yüklenenlerin kapsamını değiştirirsiniz. Neyin yüklenip neyin yüklenmediğini görmek için veri erişim koduna bakmak üzere arayüzün arkasına bakıyorsunuz.

Şimdi, endişelerin ayrılması, geliştiricinin mümkün olduğunca kodun bir yönüne bir kerede odaklanmasına izin vermelidir. Arabirim tekniği, yüklenen bu veri NASIL NASIL yüklenir, NASIL yüklenir, NE ZAMAN yüklenir ve NERE yüklenir.

Sonuç: Oldukça düşük ayrılma!

Yavaş yüklenme

Veriler talep üzerine yüklenir. Veri yükleme çağrıları, bir özelliğe erişmenin sonucu döndürmeden önce bir sql sorgusunun yürütülmesine neden olabileceği nesne grafiğinin içinde gizlenir.

foreach (var line in order.OrderLines)
{
    total += line.Price;
}

Artıları: Veri erişiminin 'NE ZAMAN, NEREDE ve NASIL' alanı, etki alanı mantığına odaklanan geliştiriciden gizlenir. Toplamda veri yüklemeyle ilgili hiçbir kod yoktur. Yüklenen veri miktarı kodun gerektirdiği kesin miktar olabilir.

Eksileri: Bir performans sorunu ile vurulduğunda, genel bir "herkese uygun" bir çözüm olduğunda düzeltmek zordur. Tembel yükleme genel olarak daha kötü performansa neden olabilir ve tembel yükleme uygulamak zor olabilir.

Rol Arayüzü / İstekli Getirme

Her kullanım durumu, toplu sınıf tarafından uygulanan bir Rol Arayüzü aracılığıyla açık hale getirilir ve kullanım durumu başına veri yükleme stratejilerinin ele alınmasını sağlar.

Getirme stratejisi şöyle görünebilir:

public class BillOrderFetchingStrategy : ILoadDataFor<IBillOrder, Order>
{
    Order Load(string aggregateId)
    {
        var order = new Order();

        order.Data = GetOrderLinesWithPrice(aggregateId);
    
        return order;
    }

}
   

Daha sonra toplamınız şöyle görünebilir:

public class Order : IBillOrder
{
    void BillOrder(BillOrderCommand command)
    {
        foreach (var line in this.Data.OrderLines)
        {
            total += line.Price;
        }

        etc...
    }
}

BillOrderFetchingStrategy, toplamayı oluşturmak için kullanılır ve daha sonra toplam işini yapar.

Artıları: Kullanım durumuna göre özel kod sağlar ve en iyi performansı sağlar. Arabirim Ayrışma İlkesi ile uyumludur . Karmaşık kod gereksinimi yoktur. Agrega ünite testleri yükleme stratejisini taklit etmek zorunda değildir. Genel yükleme stratejisi çoğu durumda kullanılabilir (örneğin "tümünü yükle" stratejisi) ve gerektiğinde özel yükleme stratejileri uygulanabilir.

Eksileri: Geliştirici hala alan kodunu değiştirdikten sonra getirme stratejisini ayarlamalı / gözden geçirmelidir.

Getirme stratejisi yaklaşımıyla, iş kurallarındaki bir değişiklik için kendinizi özel getirme kodunu değiştirirken bulabilirsiniz. Endişelerin mükemmel bir şekilde ayrılması değil, ancak daha sürdürülebilir ve ilk seçenekten daha iyidir. Getirme stratejisi, NASIL, NE ZAMAN ve NEREDE verileri yüklenir. Endişelerin daha iyi ayrılması, tek bir boyutun tüm tembel yükleme yaklaşımına uyması gibi esnekliğini kaybetmeden.


Teşekkürler, bağlantıları kontrol edeceğim. Peki cevabınızda 'endişelerin ayrılması' ile 'buna hiç erişim yok' diye kafa karıştırıyor musunuz? Kuşkusuz çoğu insan, kalıcılık katmanının Varlıkların bulunduğu katmandan ayrı tutulması gerektiği konusunda hemfikirdir. Ancak bu, 'varlıklar çok genel bir uygulama-agnostik yoluyla bile kalıcılık katmanını görememelidir. arayüz'.
codeulike

Bir arayüz üzerinden veri yüklemek veya yüklemek, iş kurallarını uygularken hala veri yüklemekle ilgileniyorsunuz. Yine de birçok insanın endişelerin bu şekilde ayrılmasına neden olduğunu kabul ediyorum, belki de tek sorumluluk ilkesi kullanmak için daha iyi bir terim olurdu.
ttg

1
Son yorumunuzu nasıl ayrıştıracağınızdan emin değilim, ancak iş kurallarını işlerken verilerin yüklenmemesi gerektiğini düşündüğünüzü düşünüyor musunuz? Bunun kuralların 'daha saf' olmasını sağlayacağını görüyorum. Ancak birçok iş kuralı türünün diğer verilere başvurması gerekecektir - bunun ayrı bir nesne tarafından önceden yüklenmesini mi öneriyorsunuz?
codeulike

@codeulike: Cevabımı güncelledim. Kesinlikle gerekli olduğunu düşünüyorsanız, ancak alan adı modelinize veri erişim kodu satırları eklemenizi gerektirmeyen (ör. Tembel yük) iş kuralları sırasında veri yükleyebilirsiniz. Tasarladığım etki alanı modellerinde, veriler genellikle söylediğin gibi önceden yüklenir. İş kurallarını yürütmenin genellikle aşırı miktarda veri gerektirmediğini fark ettim.
ttg


12

Ne harika bir soru. Aynı keşif yolundayım ve internetteki cevapların çoğu çözüm getirdikleri kadar sorun getiriyor gibi görünüyor.

Yani (şimdiden bir yıl katılmadığım bir şey yazma riski altında) şimdiye kadar yaptığım keşiflerim.

Her şeyden önce, zengin bir alan adı modelini seviyoruz bize yüksek keşfedilebilirlik (bir agrega ile yapabileceklerimiz) ve okunabilirlik (etkileyici yöntem çağrıları) sağlayan .

// Entity
public class Invoice
{
    ...
    public void SetStatus(StatusCode statusCode, DateTime dateTime) { ... }
    public void CreateCreditNote(decimal amount) { ... }
    ...
}

Bir varlığın yapıcısına herhangi bir hizmet enjekte etmeden bunu başarmak istiyoruz, çünkü:

  • Yeni bir davranışın (yeni bir hizmet kullanan) oluşturulması, yapıcı değişikliğine yol açabilir, yani değişiklik, varlığı örnekleyen her satırı etkiler !
  • Bu hizmetler modelin bir parçası değildir , ancak yapıcı enjeksiyonu bunların olduğunu düşündürür.
  • Genellikle bir hizmet (arabirimi bile), alanın bir parçası olmaktan çok bir uygulama ayrıntısıdır. Etki alanı modelinde bir dışa dönük bir bağımlılığa .
  • Olabilir İşletmenin bu bağımlılıklar olmadan neden var kafa karıştırıcı olabilir. (Kredi notu servisi, kredi notu ile hiçbir şey yapmayacağım bile ...)
  • Anlaşmayı zorlaştıracak, test etmek zorlaşır .
  • Sorun kolayca yayılıyor, çünkü bunu içeren diğer varlıklar aynı bağımlılıkları alacaktı - ki bunlar çok benziyor olabilir doğal olmayan bağımlılıklar .

Peki bunu nasıl yapabiliriz? Şimdiye kadarki sonucum, yöntem bağımlılıklarının ve çift ​​sevkıyatın iyi bir çözüm sağlamasıdır.

public class Invoice
{
    ...

    // Simple method injection
    public void SetStatus(IInvoiceLogger logger, StatusCode statusCode, DateTime dateTime)
    { ... }

    // Double dispatch
    public void CreateCreditNote(ICreditNoteService creditNoteService, decimal amount)
    {
        creditNoteService.CreateCreditNote(this, amount);
    }

    ...
}

CreateCreditNote()artık kredi notları oluşturmaktan sorumlu bir hizmet gerektiriyor. Bu kullanır çift gönderim tamamen işi boşaltma sırasında, sorumlu servise Keşfedilebilirliği sürdürmek gelen Invoicevarlık.

SetStatus()şimdi işin bir kısmını gerçekleştirecek olan bir kaydediciye basit bir bağımlılığı var .

İkincisi için, istemci kodunda işleri kolaylaştırmak için bunun yerine bir IInvoiceService. Sonuçta, fatura günlüğü bir faturaya oldukça özgü görünüyor. Böyle bir tek IInvoiceService, çeşitli operasyonlar için her türlü mini servis ihtiyacını önlemeye yardımcı olur. Dezavantajı, bu hizmetin tam olarak ne yapacağını belirsiz hale getirmesidir . Hatta çift ​​sevkiyat gibi görünmeye başlayabilir , ancak işin çoğu hala SetStatus()kendi içinde yapılır .

Niyetimizi açığa vurma umuduyla hala 'logger' parametresini adlandırabiliriz. Yine de biraz zayıf görünüyor.

Bunun yerine, IInvoiceLogger(zaten kod örneğinde yaptığımız gibi) bir istemeyi tercih ve IInvoiceServicebu arayüzü uygulamak var. İstemci kodu tekini IInvoiceServiceherkes için kullanabilirInvoice böylesine çok özel, faturaya özgü bir 'mini hizmet' yöntemler , yöntem imzaları hala ne istediklerini açıkça ortaya koymaktadır.

Depoları açık bir şekilde ele almadığımı fark ettim. Kaydedici bir havuzdur veya kullanır, ancak daha açık bir örnek vereyim. Depoya yalnızca bir veya iki yöntemde ihtiyaç duyulursa aynı yaklaşımı kullanabiliriz.

public class Invoice
{
    public IEnumerable<CreditNote> GetCreditNotes(ICreditNoteRepository repository)
    { ... }
}

Aslında, bu her zaman sorun yaratan tembel yüklere bir alternatif sağlar .

Güncelleme: Aşağıdaki metni tarihi amaçlar için bıraktım, ancak tembel yüklerden% 100 uzak durmanızı öneririm.

Gerçek, mülke dayalı tembel yükler için şu anda yapıcı enjeksiyon kullanıyorum, ancak kalıcılık cahil bir şekilde.

public class Invoice
{
    // Lazy could use an interface (for contravariance if nothing else), but I digress
    public Lazy<IEnumerable<CreditNote>> CreditNotes { get; }

    // Give me something that will provide my credit notes
    public Invoice(Func<Invoice, IEnumerable<CreditNote>> lazyCreditNotes)
    {
        this.CreditNotes = new Lazy<IEnumerable<CreditNotes>>() => lazyCreditNotes(this));
    }
}

Bir yandan, Invoiceveritabanından bir yükleyen bir havuz , karşılık gelen kredi notlarını yükleyecek bir işleve serbest erişime sahip olabilir ve bu işlevi Invoice.

Öte yandan, gerçek bir yeni oluşturan kod Invoiceyalnızca boş bir liste döndüren bir işlevi iletir:

new Invoice(inv => new List<CreditNote>() as IEnumerable<CreditNote>)

(Bir gelenek ILazy<out T>bizi çirkin oyunculardan kurtarabilirdi IEnumerable, ama bu tartışmayı zorlaştırır.)

// Or just an empty IEnumerable
new Invoice(inv => IEnumerable.Empty<CreditNote>())

Düşüncelerinizi, tercihlerinizi ve geliştirmelerinizi duymaktan memnuniyet duyarım!


3

Bana göre bu DDD'ye özgü olmaktan ziyade genel iyi OOD ile ilgili uygulama gibi görünüyor.

Düşünebileceğim nedenler:

  • Endişelerin ayrılması (İşletmeler, kullanım senaryosuna bağlı olarak aynı işletmenin devam edeceği çoklu stratejiler olabileceğinden, var olmalarından ayrılmalıdır.)
  • Mantıksal olarak, işletmeler depoların faaliyet gösterdiği seviyenin altında bir seviyede görülebilir. Alt seviye bileşenler, üst seviye bileşenler hakkında bilgi sahibi olmamalıdır. Bu nedenle girişler Depolar hakkında bilgi sahibi olmamalıdır.

2

sadece Vernon Vaughn bir çözüm sunuyor:

Toplama davranışını çağırmadan önce bağımlı nesneleri aramak için bir havuz veya etki alanı hizmeti kullanın. Bir istemci uygulama hizmeti bunu kontrol edebilir.


Ama bir Varlıktan değil.
51'de ssmith

Kaynak Vernon Vaughn IDDD Kaynak: public class Calendar uzatır EventSourcedRootEntity {... public CalendarEntry scheduleCalendarEntry (CalendarIdentityService aCalendarIdentityService,
Teimuraz


1

Tüm bu ayrı katman vızıltısı görünmeden önce nesne yönelimli programlama kodlamayı öğrendim ve ilk nesnelerim / sınıflarım doğrudan veritabanıyla eşleşti.

Sonunda, başka bir veritabanı sunucusuna geçmek zorunda olduğum için bir ara katman ekledim. Aynı senaryoyu birkaç kez gördüm / duydum.

Veri erişimini ("Depo" olarak da bilinir) iş mantığınızdan ayırmanın, Domain Driven Design kitabını düşünerek birkaç kez yeniden keşfedilen şeylerden biri olduğunu düşünüyorum.

Şu anda pek çok geliştiricinin yaptığı gibi 3 katman (GUI, Mantık, Veri Erişimi) kullanıyorum, çünkü iyi bir teknik.

Verileri bir Repositorykatmana ayırma (akaData Access katmana) sadece bir kural değil, iyi bir programlama tekniği gibi görülebilir.

Birçok metodoloji gibi, UYGULANMADIYLA başlayıp programınızı anladıktan sonra nihayetinde güncellemek isteyebilirsiniz.

Alıntı: İlyada Homer tarafından tamamen icat edilmedi, Carmina Burana Carl Orff tarafından tamamen icat edilmedi ve her iki durumda da başkalarını işe koyan kişi, tüm togheter, krediyi aldı ;-)


1
Teşekkürler, ancak veri erişimini iş mantığından ayırmak istemiyorum - bu çok geniş bir anlaşma olduğu çok açık bir şey. Neden S # arp gibi DDD mimarilerinde Varlıkların veri erişim katmanıyla 'konuşmasına' izin verilmediğini soruyorum. Hakkında çok fazla tartışma bulabildiğim ilginç bir düzenleme.
codeulike

0

Bu Eric Evans Domain Driven Design kitabından mı geldi yoksa başka bir yerden mi geldi?

Eski şeyler. Eric'in kitabı biraz daha vızıltı yaptı.

Bunun arkasındaki akıl yürütme için bazı iyi açıklamalar nelerdir?

Nedeni basittir - insan aklı belirsiz bir şekilde ilişkili çoklu bağlamlarla karşılaştığında zayıflar. Belirsizliklere yol açarlar (Güney / Kuzey Amerika'daki Amerika, Güney / Kuzey Amerika anlamına gelir), belirsizlik, zihin her "dokunuşunda" bilginin sürekli olarak eşlenmesine ve kötü üretkenlik ve hatalar olarak özetlenmesine yol açar.

İş mantığı mümkün olduğunca açık bir şekilde yansıtılmalıdır. Yabancı anahtarlar, normalleştirme, nesne ilişkisel haritalama tamamen farklı bir alandan - bunlar teknik, bilgisayarla ilgili.

Benzer şekilde: nasıl yazacağınızı öğreniyorsanız, kalemin nerede yapıldığı, mürekkebin neden kağıt üzerinde tutulduğu, kağıdın ne zaman icat edildiği ve diğer ünlü Çin icatlarının ne olduğu ile ilgili bir sorumluluk taşımamalısınız.

edit: Açıklığa kavuşturmak için: Veri erişimini iş mantığından ayrı bir katmana ayırmanın klasik OO uygulaması hakkında konuşmuyorum - DDD'de, Varlıkların verilerle konuşması gerekmeyen belirli düzenleme hakkında konuşuyorum erişim katmanına hiç (yani Havuz nesnelerine referans tutmaları gerekmez)

Neden yukarıda bahsettiğim aynı. Burada sadece bir adım daha var. Eğer varlıklar tamamen olabiliyorlarsa (en azından ona yakınsa) neden kısmen ısrar cahil olmalılar? Modelimizin sahip olduğu daha az etki alanı ilgisizliği - zihnimizin yeniden yorumlanması gerektiğinde daha fazla nefes alma alanı.


Sağ. Peki, kalıcı bir cahil bir Varlık, kalıcılık katmanıyla konuşmasına bile izin verilmiyorsa Business Logic'i nasıl uygular? Keyfi diğer varlıklardaki değerlere bakması gerektiğinde ne yapar?
codeulike

Varlığınızın keyfi olarak diğer varlıklardaki değerlere bakması gerekiyorsa, muhtemelen bazı tasarım sorunlarınız vardır. Belki de daha uyumlu olmaları için sınıfları bölmeyi düşünün.
cdaq

0

Carolina Lilientahl'den alıntı yapmak için, "Desenler döngüleri önlemelidir" https://www.youtube.com/watch?v=eJjadzMRQAk , burada sınıflar arasındaki döngüsel bağımlılıkları ifade eder. Agregaların içindeki depolarda, tek neden olarak nesne navigasyonunun uygunluğundan döngüsel bağımlılıklar yaratma cazibesi vardır. Prograhammer tarafından yukarıda bahsedilen ve Vernon Vaughn tarafından önerilen ve kök örneklerinin yerine diğer kümelere referans verilen kalıp, (bu kalıp için bir ad var mı?) Diğer çözümlere yönlendirebilecek bir alternatif önermektedir.

Sınıflar arasında döngüsel bağımlılık örneği (itiraf):

(Zaman0): İki sınıf, Sample ve Well, birbirini ifade eder (döngüsel bağımlılık). Kuyu Örnek anlamına gelir ve Örnek Kuyu, kolaylık dışında (bazen döngü örnekleri, bazen bir plakadaki tüm kuyu döngü) anlamına gelir. Numunenin yerleştirildiği Kuyu'ya referans vermediği durumları hayal edemedim.

(Zaman1): Bir yıl sonra, birçok kullanım durumu uygulandı .... ve şimdi Örnek'in yerleştirildiği Kuyu'ya referans vermemesi gereken durumlar var. Bir çalışma adımında geçici plakalar var. Burada bir kuyu, bir başka plaka üzerindeki bir kuyuya karşılık gelen bir numuneye karşılık gelir. Bu nedenle, bazen birisi yeni özellikler uygulamaya çalıştığında garip davranışlar ortaya çıkar. Girmesi zaman alır.

Tembel yüklemenin olumsuz yönleri hakkında yukarıda belirtilen bu makalede de bana yardımcı oldu .


-1

İdeal dünyada DDD, Varlıkların veri katmanlarına referans vermemesi gerektiğini önermektedir. ama biz ideal dünyada yaşamıyoruz. Etki alanlarının bağımlılığı olmayabilecekleri iş mantığı için diğer etki alanı nesnelerine başvurmaları gerekebilir. Varlıkların değerleri okumak için sadece okuma amacıyla veri havuzu katmanına başvurması mantıklıdır.


Hayır, bu, varlıklara gereksiz eşleşmeyi getirir, SRP'yi ve Endişelerin Ayrılmasını ihlal eder ve varlığın kalıcılıktan çıkarılmasını zorlaştırır (çünkü serileştirme süreci artık işletmenin sıklık yaptığı hizmetleri / depoları da enjekte etmelidir).
51'de ssmith
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.