Bir CQRS Komutu tam olarak nasıl doğrulanmalı ve bir etki alanı nesnesine dönüştürülmelidir?


22

Ben yoksul adamın adapte edilmiş CQRS 1 Bir veri deposunda granül verilere sahip olan esneklik seviyorum çünkü analiz için büyük olanaklar sağlayan şimdi oldukça zaman ve böylece iş değeri artırmak ve okur arttırılmış performans için denormalized verileri içeren başka gerektiğinde .

Fakat ne yazık ki, başından beri, bu tür bir mimaride tam olarak iş mantığını yerleştirmem gereken sorunla mücadele ediyordum.

Anladığım kadarıyla, bir komut niyeti iletmek için bir araçtır ve kendi başına bir etki alanı ile bağları yoktur. Bunlar temelde veridir (dilsiz - dilerseniz) transfer nesneleridir. Bu, komutları farklı teknolojiler arasında kolayca aktarılabilir kılmaktır. Aynısı, başarıyla tamamlanan olaylara verilen yanıtlar olarak da olaylar için geçerlidir.

Tipik bir DDD uygulamasında iş mantığı varlıklar, değer nesneleri, toplu kökler içinde bulunur, bunlar hem veriler hem de davranış bakımından zengindir. Ancak bir komut, bir etki alanı nesnesi değildir; bu nedenle, verilerin etki alanı gösterimleriyle sınırlı kalmamalıdır, çünkü bu bunlar üzerinde çok fazla baskı oluşturur.

Yani asıl soru şudur: Mantık tam olarak nerede?

Bu mücadeleyle en sık karşılaştığımı, değerlerinin birleşimi hakkında bazı kurallar koyan oldukça karmaşık bir küme inşa etmeye çalıştığımı öğrendim. Ayrıca, etki alanı nesnelerini modellerken, bir nesnenin bir yönteme ne zaman ulaştığını bilmek geçerli bir durumda olduğunu bilerek , başarısız hızlı paradigmasını izlemeyi severim .

Bir kümenin Cariki bileşen kullandığını varsayalım :

  • Transmission,
  • Engine.

Hem Transmissionve Enginedeğer nesneler süper türleri olarak temsil edilir ve uygun alt türleri vardır, vardır Automaticve Manualyayınlar ya Petrolve Electricmotorlar sırasıyla.

Bu alanda, başarıyla oluşturuldu kendi a yaşayan Transmissionolsun, Automaticya Manual, ya bir iki tip Enginetamamen gayet iyi. Ama Caragrega sadece uygulanabilir bir kaç yeni kurallar, tanıtır Transmissionve Enginenesneler aynı bağlamda kullanılır. Yani:

  • Bir araba Electricmotor kullandığında , izin verilen tek şanzıman tipidir Automatic.
  • Bir araba Petrolmotoru kullandığında , her iki türüne de sahip olabilir Transmission.

Bu bileşen kombinasyonu ihlalini bir komut oluşturma düzeyinde yakalayabilirdim, ancak daha önce de belirttiğim gibi, yapılmaması gerektiğini anladığımdan, komut daha sonra etki alanı katmanıyla sınırlı olması gereken iş mantığını içerecektir.

Seçeneklerden biri, bu iş mantığı doğrulamasını doğrulayıcının kendisine komut vermek üzere taşımaktır, ancak bu da doğru görünmemektedir. Komutu yapıştıracağım, alıcılar tarafından elde edilen özelliklerini kontrol edip doğrulayıcıda karşılaştıracağımı ve sonuçları denetleyeceğimi hissediyorum. Bu bana Demeter yasasını ihlal ediyor gibi bağırıyor .

Söz konusu doğrulama seçeneğini atmak, uygun görünmediğinden, birinin komutu kullanması ve toplamı ondan oluşturması gerektiği anlaşılmaktadır. Peki bu mantık nerede olmalı? Somut bir komutun işlenmesinden sorumlu komut işleyicisinin içinde mi olmalı? Ya da belki de komut doğrulayıcıda mı olmalı (Ben de bu yaklaşımı sevmiyorum)?

Şu anda bir komut kullanıyorum ve sorumlu komut işleyicisi içinde ondan bir toplama oluşturuyorum. Ancak bunu yaptığımda, bir komut doğrulayıcısına sahip olsam bile hiçbir şey içermez, çünkü CreateCarkomut mevcutsa ayrı durumlar için geçerli olduğunu bildiğim bileşenler içerir, ancak toplam farklı olabilir.


Farklı doğrulama işlemlerini karıştıran farklı bir senaryo düşünelim - bir CreateUserkomut kullanarak yeni bir kullanıcı yaratın.

Komut, Idoluşturulacak kullanıcıları ve bunlardan birini içerir Email.

Sistem, kullanıcının e-posta adresi için aşağıdaki kuralları belirtir:

  • eşsiz olmalı,
  • boş olmamalı,
  • en fazla 100 karakter içermelidir (en fazla db sütunu uzunluğu).

Bu durumda, benzersiz bir e-postaya sahip olmak bir iş kuralı olsa da, bir toplu halde kontrol etmek çok az mantıklıdır, çünkü sistemdeki tüm mevcut e-posta kümesini bir belleğe yüklemem ve komuttaki e-postayı kontrol etmem gerekir. agregaya karşı ( Eeeek! Bir şey, bir şey, performans.). Bu nedenle, bu kontrolü UserRepositorybir bağımlılık olarak alacak ve depoda e-postaya sahip olan bir kullanıcının zaten mevcut olup olmadığını kontrol etmek için kullanacak olan komut doğrulayıcısına taşırdım .

Buna gelince aniden diğer iki e-posta kuralını komut doğrulayıcısına da koymak mantıklı geliyor. Ama bir his kuralları dahilinde gerçekten mevcut olmalıdır sahip Useragrega ve komut doğrulayıcı sadece eşsizliği kontrol etmelidir ve doğrulama başarılı olursa ben oluşturmaya devam gerektiğini Userde agrega CreateUserCommandHandlerve kaydedilmesine bir depo geçmek.

Böyle hissediyorum çünkü deponun kaydetme yöntemi, toplama geçtikten sonra tüm değişmezlerin yerine getirilmesini sağlayan bir toplama kabul etme olasılığı yüksektir. Mantık (örneğin boşluksuzluk) sadece komut onaylama kendiliğinden mevcutsa, başka bir programcı bu onaylamayı tamamen atlayabilir ve kaydetme yöntemini doğrudan ölümcül bir veritabanı hatasına yol açabilecek UserRepositorybir Usernesneyle çağırabilir; Çok uzun oldu.

Bu karmaşık doğrulama ve dönüşümleri bizzat nasıl ele alıyorsunuz? Çözümümden çoğunlukla memnunum, ancak fikirlerim ve yaklaşımlarımın seçimlerden oldukça mutlu olmak için tamamen aptal olmadığına dair bir onay istiyorum. Tamamen farklı yaklaşımlara tamamen açığım. Şahsen denediğiniz ve sizin için çok iyi çalıştığınız bir şeye sahipseniz, çözümünüzü görmeyi çok isterim.


1 RESTful sistemler oluşturmaktan sorumlu bir PHP geliştiricisi olarak çalışmak, benim CQRS hakkındaki yorumumun , bazen komutların eşzamanlı olarak işlenmesi gereği nedeniyle komutlardan sonuçların döndürülmesi gibi standart eşzamansız-komut işleme yaklaşımından biraz farklıdır .


bence bazı kodlara ihtiyacım var. Komut nesneleriniz neye benziyor ve bunları nerede yaratıyorsunuz?
Ewan

@Ewan Kod numunelerini daha sonra bugün veya yarın ekleyeceğim. Birkaç dakika içinde bir gezi için ayrılıyor.
Andy,

Bir PHP programcısı olmak için CQRS + ES uygulamasına bir göz atmayı öneriyorum: github.com/xprt64/cqrs-es
Constantin Galbenu

@CtanttantGALBENU Greg Young'un CQRS hakkındaki yorumunu haklı bulmalı mıyız (muhtemelen yapmalıyız), o zaman CQRS hakkındaki anlayışınız yanlıştır - ya da en azından PHP uygulamanız. Komutlar doğrudan kümeler tarafından kullanılmamalıdır. Komutlar, toplamalarda daha sonra durum çoğaltmaları için kullanılacak olayları üreten değişiklikler üretebilecek komut işleyicileri tarafından ele alınacaktır.
Andy

Yorumlarımızın farklı olduğunu sanmıyorum. Sadece DDD'ye (taktik Agrega seviyesinden) daha fazla kazmanız veya gözlerinizi daha geniş açmanız gerekiyor. CQRS'yi uygulamanın en az iki tarzı vardır. Onlardan birini kullanıyorum. Uygulamam Aktör modeline daha çok benziyor ve Uygulama katmanını çok ince yapıyor, bu her zaman iyi bir şey. Bu uygulama hizmetlerinin içinde çok sayıda kod çoğaltması olduğunu gözlemledim ve bunları a ile değiştirmeye karar verdim CommandDispatcher.
Constantin Galbenu

Yanıtlar:


22

Aşağıdaki cevap, komutların doğrudan topaklara ulaştığı , cqrs.nu tarafından desteklenen CQRS stili bağlamındadır . Bu mimari tarzda uygulama hizmetleri, kümeyi tanımlayan, yükleyen, komutu gönderen ve daha sonra kümeye devam eden (Olay kaynağı kullanılıyorsa bir dizi olay olarak) bir altyapı bileşeni ( CommandDispatcher ) ile değiştirilir .

Yani asıl soru şudur: Mantık tam olarak nerede?

Birden çok tür (doğrulama) mantığı vardır. Genel fikir, mantığı mümkün olduğunca erken çalıştırmaktır - isterseniz hızlıca başarısız olursunuz. Yani, durumlar aşağıdaki gibidir:

  • komut nesnesinin kendisinin yapısı; komutun yapıcısı, komutun yaratılması için bulunması gereken zorunlu alanlara sahiptir; bu ilk ve en hızlı doğrulamadır; Bu açıkça komutta yer almaktadır.
  • düşük seviyeli alan doğrulama, bazı alanların boşluksuzluğu (kullanıcı adı gibi) veya format (geçerli bir e-posta adresi) gibi. Bu tür bir doğrulama, yapıcıda komutun içinde yer almalıdır. Bir isValidyönteme sahip olmak için başka bir stil daha var, ancak başarılı bir komut başlatmanın yeterli olduğu durumlarda birisinin bu yöntemi çağırmayı hatırlaması gerekeceği için bu bana anlamsız geliyor.
  • Ayrı command validators, bir komutu onaylama sorumluluğuna sahip sınıflar. Birden fazla kümeden veya dış kaynaktan gelen bilgileri kontrol etmem gerektiğinde bu tür bir doğrulama kullanıyorum. Bunu bir kullanıcı adının benzersizliğini kontrol etmek için kullanabilirsiniz. Command validatorshavuzlar gibi enjekte edilen herhangi bir bağımlılığa sahip olabilir. Bu doğrulamanın sonunda toplamla tutarlı olduğunu unutmayın (yani, kullanıcı oluşturulduğunda, aynı kullanıcı adına sahip başka bir kullanıcı bu arada oluşturulabilir)! Ayrıca, buraya kümenin içinde bulunması gereken mantığı koymaya çalışmayın! Komut doğrulayıcıları, olaylara dayalı komutlar üreten Sagas / İşlem yöneticilerinden farklıdır.
  • komutları alan ve işleyen toplu yöntemler. Bu, gerçekleşen son (tür) doğrulamadır. Toplam, verileri komuttan ayıklar ve kabul ettiği temel iş mantığını kullanarak (durumundaki değişiklikleri gerçekleştirir) veya reddeder. Bu mantık, tutarlı bir şekilde kontrol edilir. Bu son savunma hattı. Örneğinizde, kural When a car uses Electric engine the only allowed transmission type is Automaticburada kontrol edilmelidir.

Böyle hissediyorum çünkü deponun kaydetme yöntemi, toplama geçtikten sonra tüm değişmezlerin yerine getirilmesini sağlayan bir toplama kabul etme olasılığı yüksektir. Mantık (örneğin boşluğu olmayan) sadece komut onaylama kendisinin içinde mevcutsa, başka bir programcı bu onaylamayı tamamen atlayabilir ve doğrudan bir ölümcül veri tabanı hatasına yol açabilecek bir User nesnesiyle UserRepository'deki kaydetme yöntemini çağırabilir. çok uzun olabilirdi.

Yukarıdaki teknikleri kullanarak hiç kimse geçersiz komutlar oluşturamaz ya da toplamların içindeki mantığı atlayamaz. Komut doğrulayıcıları otomatik olarak + yüklenir, CommandDispatcherböylece hiç kimse doğrudan topluluğa bir komut gönderemez. Biri toplamı bir komuttan geçirerek bir yöntem olarak adlandırabilir, ancak değişikliklere devam edemediği için bunun anlamsız / zararsız olacağı söylenebilir.

RESTful sistemler oluşturmaktan sorumlu bir PHP geliştiricisi olarak çalışmak, benim CQRS hakkındaki yorumumun, bazen komutların eşzamanlı olarak işlenmesi gereği nedeniyle komutlardan sonuçların döndürülmesi gibi standart eşzamansız-komut işleme yaklaşımından biraz farklıdır.

Ayrıca bir PHP programcısıyım ve komut işleyicilerimden (formdaki toplu yöntemler) hiçbir şey döndürmüyorum handleSomeCommand. Bununla birlikte, sıklıkla, müşteriye / tarayıcıya HTTP response, örneğin yeni oluşturulan toplu kökün kimliği veya bir okuma modelindeki bir şey gibi bilgiler veririm, ancak toplam komut yöntemlerinden hiçbir zaman hiçbir şey döndürmem (gerçekten asla ). Komutun kabul edildiği (ve işlendiği - basit - senkronize PHP işlemeden bahsediyoruz, değil mi?) Basit bir gerçektir.

Tarayıcıya bir şey döndürüyoruz (ve hala kitapta CQRS yapıyoruz) çünkü CQRS yüksek seviyede bir mimari değil .

Komut doğrulayıcıların nasıl çalıştığına bir örnek:

Komutun yolu, validasyonlayıcılar yoluyla Toplam yolunda


Doğrulama stratejinizle ilgili olarak, iki numaralı nokta, mantığın sıklıkla çoğaltılacağı muhtemel bir yer olarak bana atlıyor. Kuşkusuz, Kullanıcının toplamı boş olmayan ve iyi biçimlendirilmiş bir e-postayı doğrulamasını ister. Bir ChangeEmail komutu sunduğumuzda bu ortaya çıkar.
kral-slayt

@ king-side-slide EmailAddress, kendi kendini doğrulayan bir değer nesnesiniz yoksa .
Constantin Galbenu

Bu tamamen doğru. Kişi EmailAddressçoğaltmayı azaltmak için bir kapsül içine alabilir . Daha da önemlisi, bunu yaparken de mantığı emrinizden ve etki alanınızdan uzaklaştırıyor olacaksınız. Bunun çok uzağa alınabileceğini belirtmekte fayda var. Genellikle benzer bilgi parçaları (değer nesneleri) kimin kullandığına bağlı olarak farklı doğrulama gereksinimlerine sahip olabilir. EmailAddressuygun bir örnektir, çünkü bu değer kavramının tamamı küresel doğrulama gereksinimlerine sahiptir.
Kral-slayt

Benzer şekilde, bir "komut validator" fikri gereksiz görünüyor. Amaç, geçersiz komutların oluşturulmasını ve gönderilmesini engellemek değildir. Amaç, yürütmelerini engellemektir. Örneğin, istediğim verileri bir URL ile iletebilirim. Geçersizse, sistem isteğimi reddeder. Komut hala oluşturulur ve gönderilir. Bir komut doğrulama için birden fazla toplama gerektiriyorsa (yani, e-posta benzersizliğini kontrol etmek için bir Kullanıcılar topluluğu), bir etki alanı hizmeti daha uygun olur. "X validator" gibi nesneler genellikle verilerin davranıştan ayrıldığı anemik bir modelin işaretidir.
kral tarafındaki slayt

1
@ king-side-slide Bir somut örnek UserCanPlaceOrdersOnlyIfHeIsNotLockedValidator. Bunun, Siparişler'in ayrı bir etki alanı olduğunu görebilirsiniz, böylece OrderAggregate'in kendisi tarafından doğrulanamaz.
Constantin Galbenu

6

DDD'nin temel öncüllerinden biri, etki alanı modellerinin kendilerini doğrulamasıdır. Bu kritik bir kavramdır, çünkü iş kurallarınızın uygulandığından emin olmak için etki alanınızı sorumlu taraf olarak yükseltir. Aynı zamanda etki alanı modelinizi geliştirme için odak noktası olarak tutar.

Bir CQRS sistemi (doğru bir şekilde belirttiğiniz gibi), kendi yapışkan mekanizmasını uygulayan genel bir alt alanı temsil eden bir uygulama detayıdır. Modeliniz hiçbir şekilde bağlı olmalıdır herhangi iş kurallarına göre davranmaya CQRS altyapısının parçası. DDD'nin amacı, bir sistemin davranışını , sonucun temel iş alan adınızın işlevsel gereksinimlerinin yararlı bir soyutlaması olacak şekilde modellemektir . Bu davranışın herhangi bir parçasını modelinizden çıkarmak, ancak cazip olmak, modelinizin bütünlüğünü ve uyumunu azaltmaktır (ve daha az kullanışlı hale getirir).

Örneğinizi bir ChangeEmailkomut içerecek şekilde genişleterek , kurallarınızı çoğaltmanız gerekeceğinden, işletme mantığınızdan herhangi birini komut altyapınızda neden istemediğinizi mükemmel şekilde gösterebiliriz:

  • e-posta boş olamaz
  • e-posta 100 karakterden uzun olamaz
  • e-posta benzersiz olmalı

Şimdi mantığımızın alanımızda olması gerektiğinden emin olalım, hadi "nerede" konusunu ele alalım. İlk iki kural Usertopluluğumuza kolayca uygulanabilir , ancak bu son kural biraz daha farklıdır; biraz daha derin kavrayış kazanmak için daha fazla bilgi çırpma gerektiren bir tane. Yüzeyde, bu kural a için geçerli gibi görünebilir User, ancak gerçekten de geçerli değildir. Bir e-postanın "benzersizliği" ( Usersbazı kapsamlara göre) koleksiyonuna uygulanır .

Ah ha! Bunu akılda tutarak, UserRepository(hafızadaki koleksiyonunuzun Users) bu değişmezliği uygulamak için daha iyi bir aday olabileceği açıkça belli oluyor . "Kaydet" yöntemi, çekleri dahil etmek için en uygun yer olabilir ( UserEmailAlreadyExistsistisna atabileceğiniz yer ). Alternatif olarak, yeni bir alan UserServiceoluşturmaktan Usersve özniteliklerini güncellemekten bir etki alanı oluşturulabilir .

Hızlı başarısızlık iyi bir yaklaşımdır, ancak yalnızca modelin geri kalanına nerede ve ne zaman sığabileceği ile yapılabilir. Siz (geliştirici) aramanın işlemin daha derin bir yerinde başarısız olacağını bildiğiniz zaman, arızaları yakalama girişiminde işlem yapmadan önce bir uygulama servis yöntemindeki (veya komuttaki) parametreleri kontrol etmek son derece cazip olabilir. Ancak, bunu yaparken, iş kuralları değiştiğinde büyük olasılıkla birden fazla güncelleme gerektirecek şekilde çoğaltılmış (ve sızdırılmış) bilgileriniz olacaktır.


2
Buna katılıyorum. Şimdiye kadar yaptığım okuma (CQRS olmadan) bana, onaylamaları her zaman değişmeyenleri korumak için etki alanı modelinde yapması gerektiğini söyledi. Şimdi CQRS okuyorum, doğrulamayı Command nesnelerine koymamı söylüyor. Bu karşı sezgisel görünüyor. Örneğin, doğrulama işleminin Komut yerine Etki Alanı Modeli'nde yapıldığı GitHub hakkında herhangi bir örnek biliyor musunuz? +1.
w0051977
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.