Găsiți rădăcina agregată DDD
On februarie 1, 2021 by adminSă jucăm jocul preferat al tuturor, găsiți rădăcina agregată. Să folosim domeniul canonic Client / Comandă / Linii de comandă / Problemă produs. În mod tradițional, Client, comandă și produs sunt AR-urile cu Linii de comandă fiind entități în cadrul Comenzii. Logica din spatele acestui lucru este că trebuie să identificați clienții, comenzile și produsele, dar o Linie de comandă nu ar exista fără o comandă. Deci, în domeniul nostru problematic, avem o regulă de afaceri care spune că un client poate avea doar o singură comandă nedivizată. la un moment dat.
Acest lucru mută comanda sub rădăcina agregată a clientului? Cred că da. Dar, făcând acest lucru, acest lucru face ca clientul să fie destul de mare și să fie supus unor probleme de concurență mai târziu.
Sau, ce se întâmplă dacă am avea o regulă de afaceri care să ateste că un client poate comanda un anumit produs o singură dată în timpul vieții sale. Aceasta este o dovadă mai mare care impune clientului să dețină comanda.
Dar când vine vorba la expediere, își fac toate acțiunile în ceea ce privește Comanda, nu clientul. Este cam prost să trebuiască să încărcați întregul client pentru a marca o Comandă individuală ca livrată.
Acesta este ceea ce propun:
class Customer { public Guid Id {get;set;} public string Name { get; set; } public Address Address { get; set; } public IEnumerable<Order> Orders { get; set; } public void PlaceOrder(ThingsInTheOrder thingsInTheOrder) { // Make sure there aren"t any pending orders already. // Make sure they aren"t ordering a Widget if they"ve already ordered a Widget in the past. // Create an Order object and add it to the collection. Raise a domain event to trigger emails and other stuff } } class Order { public Guid Id { get; set; } public IEnumerable<OrderLine> OrderLines { get; set; } public ShippingData {get;set;} public void Ship(ShippedByPerson shippedByPerson, string trackingCode) { // Create a new ShippingData object and assign it from the data passed in. // Publish a domain event } }
Cea mai mare preocupare a mea este problema concurenței și faptul că Ordinul însuși are caracteristică s ale unei rădăcini agregate.
Răspuns
Care sunt criteriile pentru definirea unui agregat?
Să ne întoarcem la elementele de bază ale cărții albastre mari:
Agregat: Un grup de obiecte asociate care sunt tratate ca o unitate în scopul modificărilor de date . Referințele externe sunt limitate la un membru al AGGREGATE, desemnat ca rădăcină. Un set de reguli de coerență se aplică în limitele AGGREGATULUI.
Scopul este menținerea invarianților. Dar este, de asemenea, să gestionăm corect identitatea locală, adică identificarea obiectelor care nu au o semnificație singură.
Order
și Order line
aparține definitiv unui astfel de cluster. De exemplu:
- Ștergerea unui
Order
, va necesita ștergerea tuturor liniilor sale. - Ștergerea unei linii ar putea necesita renumerotarea următoarelor rânduri
- Adăugarea unei noi linii ar necesita determinarea liniei nulber pe baza tuturor celorlalte linii din același ordin.
- Modificarea unor informații despre comenzi, cum ar fi, de exemplu, moneda, ar putea afecta semnificația prețului în elementele rând (sau ar trebui să recalculeze prețurile).
Deci, aici, agregatul complet este necesar pentru a asigura reguli de coerență și invarianți.
Când opriți?
Acum, descrieți câteva reguli comerciale și susțineți că, pentru a le asigura, ar trebui să luați în considerare clientul ca parte a agregatului:
Avem un business Regula care spune că un Client poate avea o singură comandă nedivizată la un moment dat.
Desigur, de ce nu. Să vedem implicațiile: comanda va fi accesată întotdeauna prin intermediul clientului. Este viața reală? Când lucrătorii umplu cutiile pentru livrarea comenzii, vor trebui să citească codul de bare al clientului și codul de bare al comenzii pentru a accesa comanda De fapt, în general, identitatea unei comenzi este globală, nu locală pentru un client, iar această relativă independență sugerează să-l mențină în afara agregatului.
În plus, aceste reguli comerciale arată mai mult ca politici: este o decizie arbitrară a companiei de a derula procesul lor cu aceste reguli. Dacă regulile nu sunt respectate, șeful ar putea fi nemulțumit, dar datele nu sunt chiar incompatibile. Și mai mult, peste noapte ” per client o comandă nedivizată la un moment dat ” ar putea deveni ” zece comenzi nedivizate per client ” sau chiar ” independent de client, sute de comenzi nedivizate pe depozit „, astfel încât agregatul s-ar putea să nu mai fie justificat.
Răspuns
Versiune scurtă
Rațiunea pentru DDD este că obiectele de domeniu sunt abstracții care ar trebui să îndeplinească cerințele dvs. de domeniu funcțional – dacă obiectele de domeniu nu pot îndeplini cu ușurință aceste cerințe, sugerează că ați putea folosi o abstracție greșită. Obiecte de domeniu care utilizează Substantive de entitate pot duce la acele obiecte care sunt strâns legate între ele și devin obiecte „zeu” umflate și pot provoca probleme precum cel din această întrebare, cum ar fi ca „Unde este locul potrivit la p ut metoda CreateOrder? „.
Pentru a face mai ușoară identificarea rădăcinii agregate „potrivite”, luați în considerare o abordare diferită în care obiectele de domeniu se bazează pe cerințele funcționale la nivel înalt de afaceri – adicăalege substantive care fac aluzie la cerințe funcționale și / sau comportamente pe care utilizatorii sistemului trebuie să le îndeplinească.
Versiune lungă
DDD este o abordare a OO Design, care are ca rezultat un grafic de Obiecte de domeniu în Business Stratul al sistemului dvs. – Obiectele de domeniu sunt responsabile pentru satisfacerea cerințelor dvs. de afaceri la nivel înalt și, în mod ideal, ar trebui să se poată baza pe stratul de date pentru lucruri precum performanța și integritatea depozitului de date persistent.
O altă modalitate de a o privi ar putea fi punctele glonț din această listă
- Substantivele de entitate sugerează de obicei atribute de date.
- Substantivii de domeniu ar trebui să sugereze comportament
- Modelarea DDD și OO se referă la abstracții bazate pe cerințe funcționale și logică de domeniu / business de bază.
- Stratul de logică pentru afaceri este responsabil pentru satisfacerea cerințelor de domeniu la nivel înalt
Una dintre concepțiile greșite comune referitoare la DDD este că obiectele de domeniu ar trebui să se bazeze pe anumite realități fizice. „lucru” mondial (adică un substantiv pe care îl poți indica în lumea reală, atribuit cu tot felul de date / proprietăți), totuși datele / atributele acelor lucruri din lumea reală nu fac neapărat un bun punct de plecare atunci când încerci să cuiezi cerințele funcționale.
Desigur, Business Logic ar trebui să folosească aceste date, dar obiectele de domeniu în sine ar trebui să fie în cele din urmă abstracții care să reprezinte cerințe și comportamente funcționale ale domeniului.
De exemplu; substantive precum Order
sau Customer
nu implică niciun comportament și, prin urmare, sunt în general abstracții inutile pentru reprezentarea logicii de afaceri și a obiectelor de domeniu.
Atunci când căutați tipurile de abstracții care ar putea fi utile pentru reprezentarea logicii de afaceri, luați în considerare cerințele tipice pe care v-ați putea aștepta ca un sistem să le îndeplinească:
- În calitate de agent de vânzări, eu doresc să creez o comandă pentru un client nou, astfel încât să pot genera o factură pentru produsele care urmează să fie vândute cu prețurile și cantitatea lor.
- În calitate de consilier de servicii pentru clienți, doresc să anulez o comandă în așteptare, astfel încât Comanda nu este îndeplinită de către un operator de depozit.
- În calitate de consilier de servicii pentru clienți, doresc să returnez o linie de comandă, astfel încât produsul să poată fi ajustat în inventar și plata să fie rambursată prin plata inițială a clientului. metoda.
- În calitate de operator de depozit, doresc să vizualizez toate produsele dintr-o comandă în așteptare și informațiile de expediere, astfel încât să pot alege produsele și să le trimit prin curier.
- etc.
Modelarea cerințelor domeniului cu o abordare DDD
Pe baza listei de mai sus, luați în considerare câteva obiective potențiale de domeniu ts pentru un astfel de sistem de comenzi:
SalesOrderCheckout PendingOrdersStream WarehouseOrderDespatcher OrderRefundProcessor
Ca obiecte de domeniu, acestea reprezintă abstracții care își asumă proprietatea asupra diferitelor cerințe de domeniu comportamental; într-adevăr substantivele lor sugerează puternic cerințele funcționale specifice pe care le îndeplinesc.
(Poate exista și infrastructură suplimentară acolo, cum ar fi un EventMediator
pentru a trece notificări pentru observatorii care doresc să știe când a fost creată o nouă comandă sau când a fost expediată o comandă etc.).
De exemplu, SalesOrderCheckout
probabil trebuie să gestionați datele despre clienți, transport și produse, totuși nu este preocupat de nicio legătură cu comportamentul pentru expedierea comenzilor, sortarea comenzilor în așteptare sau emiterea de rambursări.
Pentru SalesOrderCheckout
pentru a-și îndeplini cerințele de domeniu include aplicarea acelor reguli comerciale, cum ar fi prevenirea clienților care comandă prea multe articole, posibil rularea unor validări și poate creșterea notificărilor pentru alte părți ale sistemului – poate face toate aceste lucruri fără a fi neapărat necesar să depindă oricare dintre celelalte obiecte.
DDD folosind Substantive de entitate pentru a reprezenta obiecte de domeniu
Ther e o serie de pericole potențiale atunci când se tratează substantive simple precum Order
, Customer
și Product
ca obiecte de domeniu; printre aceste probleme se numără cele la care faceți aluzie în întrebare:
- Dacă o metodă gestionează un
Order
, unCustomer
și unProduct
, cărui obiect de domeniu îi aparține? - Unde este rădăcina agregată pentru aceste 3 obiecte?
Dacă alegeți Substantive de entitate pentru a reprezenta obiecte de domeniu, se pot întâmpla mai multe lucruri:
-
Order
,Customer
șiProduct
sunt expuse riscului de a deveni obiecte„ zeu ” - Riscul de a ajunge la un singur
Manager
Dumnezeu-obiect pentru a lega totul împreună. - Aceste obiecte riscă să fie strâns legate între ele – poate fi greu să îndeplinești cerințele domeniului fără să treci
this
(sauself
) - Un risc de a dezvolta abstracții „scurgeri” – adicăobiectele de domeniu sunt de așteptat să expună zeci de metode
get
/set
care slăbesc încapsularea (sau, dacă nu „, atunci un alt programator probabil că mai târziu ..). - Un risc ca obiectele de domeniu să devină umflate cu un amestec complex de date comerciale (de exemplu, introducerea datelor utilizatorului printr-o interfață de utilizare) și starea tranzitorie (de exemplu, un „istoric” al acțiunilor utilizatorului când comanda a fost modificată).
DDD, OO Design și Plain Models
O concepție greșită obișnuită cu privire la DDD și OO Design este că modelele „simple” sunt cumva „ rău „sau un” anti-model „. Martin Fowler a scris un articol care descrie Modelul de domeniu anemic – dar așa cum arată clar în articol, DDD în sine ar trebui nu „contrazice” abordarea separării curate între straturi
„De asemenea, merită subliniat faptul că punerea comportamentului în obiectele domeniului nu ar trebui să contrazică abordarea solidă a utilizării stratificării pentru a separa evaluați logica domeniului din lucruri precum persistența și responsabilitățile de prezentare. Logica care ar trebui să se afle într-un obiect de domeniu este logica domeniului – validări, calcule, reguli de afaceri – oricum doriți să-l numiți. „
Cu alte cuvinte, utilizarea modelelor simple pentru păstrarea datelor de afaceri transferate între alte straturi (de exemplu, un model de comandă transmis de o aplicație de utilizator atunci când utilizatorul dorește să creeze o nouă comandă) nu este același lucru cu un „model de domeniu anemic”. Modelele de date „simple” sunt adesea cel mai bun mod de a urmări datele și de a transfera date între straturi (cum ar fi un serviciu web REST, un magazin de persistență, o aplicație sau o interfață de utilizare, etc.).
Logica de afaceri poate procesa datele din acele modele și le pot urmări ca parte a stării afacerii – dar nu vor lua neapărat proprietatea asupra acestor modele.
Rădăcina agregată
Privind din nou la exemplul de obiecte de domeniu – SalesOrderCheckout
, PendingOrdersStream
, WarehouseOrderDespatcher
, OrderRefundProcessor
nu există încă nici o rădăcină agregată evidentă, dar asta da Nu contează de fapt, deoarece aceste obiecte de domeniu au responsabilități extrem de separate, care par să nu se suprapună.
Funcțional, nu este nevoie ca SalesOrderCheckout
să vorbească cu PendingOrdersStream
deoarece sarcina fostului este completă când a adăugat o nouă comandă la baza de date; pe de altă parte, PendingOrdersStream
poate prelua noi comenzi din baza de date. Aceste obiecte nu trebuie să interacționeze cu fiecare altele direct (Poate că un mediator de evenimente ar putea oferi notificări între cele două, dar m-aș aștepta ca orice cuplare între aceste obiecte să fie foarte slabă)
Poate că rădăcina agregată va fi un container IoC care injectează unul sau mai multe dintre acele obiecte de domeniu într-un controler de interfață, furnizând și alte infrastructuri precum EventMediator
și Repository
. Sau poate că va fi un fel de serviciu ușor de orchestrator care se află deasupra straturii de afaceri.
Rădăcina agregată nu trebuie neapărat să fie un obiect de domeniu. Pentru a păstra separarea preocupărilor între obiectele de domeniu, este în general un lucru bun atunci când agregatul rădăcina este un obiect separat, fără logică de afaceri.
Comentarii
- Am votat în jos pentru că răspunsul dvs. combină concepte din Entity Framework, care este o tehnologie specifică Microsoft cu Domain Driven Design, care provine dintr-o carte scris de Eric Evans cu același nume. Aveți câteva afirmații în răspunsul dvs. care sunt în contradicție directă cu cartea DDD, iar această întrebare nu menționează nimic despre Entity Framework, dar este specificată cu DDD. ‘ nu există nici o mențiune de persistență în această întrebare, așa că nu ‘ nu văd unde sunt relevante tabelele bazei de date.
- @RibaldEddie Vă mulțumim că ați luat timp pentru a examina răspunsul și a comenta, sunt de acord că menționarea datelor persistente nu trebuie să fie cu adevărat în răspuns, așa că ‘ l-am reformulat pentru a-l elimina. Obiectivul principal al răspunsului ar putea fi rezumat ca ” Substantivele entității nu sunt adesea ‘ nume foarte bune de clase de obiecte de domeniu datorită tendinței lor de a deveniți obiecte divin strâns cuplate „, sperăm că mesajul și argumentarea cerințelor / comportamentului funcțional WRT sunt mai clare acum?
- Cartea DDD nu ‘ nu au acel concept IIRC. Are Entități, care sunt doar clase care, atunci când sunt instanțiate, au o identitate persistentă și unică, astfel încât două instanțe separate implică două lucruri unice și persistente, ceea ce contrastează cu obiectele de valoare care nu ‘ nu au identitate și nu ‘ t persistă în timp. În carte, atât entitățile, cât și obiectele Value sunt obiecte de domeniu.
Răspuns
în domeniul problemei noastre, avem o regulă de afaceri care spune că un client poate avea o singură comandă nedivizată la un moment dat.
Înainte de a merge prea adânc în gaura de iepure, ar trebui să revizuiți Greg Discuția tinerilor despre consistența setului și, în special:
Care este impactul de afaceri al unui eșec?
Deoarece în multe cazuri, răspunsul corect nu este să încercăm să prevenim nu se întâmplă un lucru greșit, dar, în schimb, să să genereze rapoarte de excepție atunci când ar putea exista o problemă.
Dar, presupunând că mai multe ordine o răspundere semnificativă față de afacerea dvs. …
Da, dacă doriți să vă asigurați că există o singură comandă nedivizată, atunci trebuie să existe un anumit agregat care să poată vedea toate comenzile unui client .
Acest agregat nu este neapărat agregatul clientului .
Ar putea fi ceva de genul unei cozi de comenzi sau a unui istoric al comenzilor, în care toate comenzile pentru un anumit clientul intră în aceeași coadă. Din ceea ce ați spus, nu are nevoie de toate datele de profil ale clientului, așa că nu ar trebui să facă parte din acest agregat.
Dar când vine vorba de livrare, aceștia își fac toate acțiunile în ceea ce privește Comanda, nu clientul.
Da, atunci când lucrați de fapt cu îndeplinire și extrage foi, vizualizarea istoric nu este deosebit de relevantă.
Vizualizarea istoric, pentru a-ți aplica invariantul, are nevoie doar de ID-ul comenzii și de starea sa de procesare curentă. Asta nu trebuie neapărat să facă parte din același agregat ca și comanda – amintiți-vă, limitele agregate se referă la gestionarea schimbării, nu la structurarea vizualizărilor.
Deci, s-ar putea să gestionați comanda ca agregat , și istoricul comenzilor ca un agregat separat și coordonează activitatea dintre cele două.
Răspuns
Tu „ai configurat un exemplu de persoană de paie. Este prea simplist și mă îndoiesc că reflectă un sistem din lumea reală. Nu aș modela entitățile respective și comportamentul lor asociat în modul pe care l-ați specificat din această cauză.
Clasele dvs. trebuie să modeleze starea unei ordine într-un mod care se reflectă în agregate multiple. De exemplu, atunci când clientul pune sistemul în starea în care trebuie procesată solicitarea comenzii clientului, aș putea crea un agregat de obiect de entitate de domeniu numit CustomerOrderRequest
sau PendingCustomerOrder
sau chiar doar CustomerOrder
, sau orice altă limbă utilizează compania și ar putea deține un indicator atât către Client, cât și către Linii de comandă și apoi să aibă o metodă ca canCustomerCompleteOrder()
care este apelat din stratul de servicii.
Acest obiect de domeniu ar conține logica de afaceri pentru a determina dacă ordinea a fost validă sau nu.
Dacă comanda ar fi validă și procesată, aș avea o modalitate de a tranzi acest obiect la un alt obiect care a reprezentat comanda procesată.
Cred că problema cu înțelegerea dvs. este că utilizați un instrument inventat un exemplu simplificat de agregate. Un PendingOrder
poate fi agregatul propriu separat de un UndeliveredOrder
și din nou separat de un sau un CancelledOrder
sau orice altceva.
Comentarii
- Deși încercarea ta la un limbaj neutru de gen este amuzant, aș observa că femeile nu stau niciodată pe câmpuri pentru a speria corbii.
- @RobertHarvey că ‘ este un lucru ciudat pe care să te concentrezi în postarea mea. Sperieturile și efigiile au apărut în mod regulat în formă feminină de-a lungul istoriei.
- Nu ‘ nu ați fi făcut distincția în postarea dvs. dacă nu ați ‘ t consideră că este important. Ca o chestiune de lingvistică, termenul este ” om de paie; ” orice rezerve cu privire la sexism sunt aproape cu siguranță atinse de despre ce naiba vorbește despre factorul ” creat prin inventarea propriului termen.
- @RobertHarvey dacă cineva știe ce paie omul înseamnă că, ‘ sunt sigur că își pot da seama ce înseamnă persoana de paie dacă nu au ‘ auzit acel termen. Ne putem concentra asupra substanței postului meu, vă rog să creați software?
Răspuns
Vaughn Vernon menționează acest lucru în cartea „Implementarea domeniului bazat pe design” la începutul capitolului 7 (Servicii):
„Deseori cea mai bună indicație că ar trebui să creați un serviciu în modelul de domeniu este atunci când operația pe care trebuie să o efectuați se simte de loc ca metodă pe un agregat sau un obiect de valoare „.
Deci, în acest caz ar putea exista un serviciu de domeniu numit „CreateOrderService” care să ia o instanță de client și lista de articole pentru comandă.
class CreateOrderService { public Order CreateOrder(Customer customer, ThingsInTheOrder thingsInTheOrder) { // Get all the orders for the customer // Check if any of the things to be ordered exist in previous orders // If none have been previously ordered, create the order and return it // Otherwise return null } }
Comentarii
- Puteți explica mai multe despre modul în care serviciul de domeniu poate ajuta la soluționarea problemelor de concurență în întrebare?
Lasă un răspuns