Hitta DDD Aggregate Root
On februari 1, 2021 by adminLåt oss spela alla favoritspel, hitta Aggregrate Root. Låt oss använda den kanoniska domänen Kund / Order / OrderLines / Produktproblem. Traditionellt är Kund, order och produkt AR: erna med OrderLines som enheter under Ordern. Logiken bakom detta är att du behöver identifiera kunder, order och produkter, men en OrderLine skulle inte existera utan en Order. Så i vår problemdomän har vi en affärsregel som säger att en kund bara kan ha en icke levererad order åt gången.
Flyttar det ordern under kundens sammanlagda rot? Jag tror det gör det. Men då gör det kunden AR ganska stor och utsätts för problem med samtidighet senare.
Eller tänk om vi hade en affärsregel som säger att en kund bara kan beställa en viss produkt en gång under sin livstid. Detta är mer bevis som kräver att kunden äger ordern.
Men när det kommer till frakt gör de alla sina handlingar på ordern, inte kunden. Det är lite dumt att behöva ladda upp hela kunden för att markera en enskild order som levererad.
Detta är vad jag föreslår:
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 } }
Min största oro är samtidighetsfrågan och det faktum att själva ordern har s av en sammanlagd rot.
Svar
Vilka är kriterierna för att definiera ett aggregat?
Låt oss gå tillbaka till grunderna i den stora blå boken:
Aggregat: Ett kluster av associerade objekt som behandlas som en enhet för ändringar av data . Externa referenser är begränsade till en medlem av AGGREGATE, betecknad som roten. En uppsättning konsekvensregler gäller inom AGGREGATE: s gränser.
Målet är att upprätthålla invarianterna. Men det är också att hantera korrekt lokal identitet, dvs identifiera objekt som inte har någon mening ensam.
Order
och Order line
tillhör definitivt ett sådant kluster. Till exempel:
- Radera en
Order
, kräver att alla dess rader tas bort. - Om du raderar en rad kan det behöva numrering av följande rader
- Om du lägger till en ny rad krävs det att radnumret bestäms baserat på alla andra rader i samma ordning.
- Att ändra viss orderinformation, som till exempel valutan, kan påverka prisets betydelse i raderna (eller kräva omberäkning av priserna).
Så här hela aggregeringen krävs för att säkerställa konsekvensregler och invarianter.
När ska du sluta?
Nu beskriver du några affärsregler och argumenterar för att du måste överväga kunden för att säkerställa dem som en del av aggregatet:
Vi har en affär s regel som säger att en kund bara kan ha en beställning som inte levereras åt gången.
Naturligtvis, varför inte. Låt oss se konsekvenserna: ordern kommer alltid att nås via kunden. Är det här i verkligheten? När arbetare fyller rutorna för att leverera ordern, måste de läsa kundens streckkod och orderstreckkoden för att få tillgång till beställningen ? I själva verket är en beställnings identitet i allmänhet global, inte lokal för en kund, och detta relativa oberoende föreslår att hålla honom utanför det sammanlagda.
Dessutom ser dessa affärsregler mer ut som policyer: det är ett godtyckligt beslut från företaget att köra sin process med dessa regler. Om reglerna inte respekteras kan chefen vara olycklig, men uppgifterna är inte riktigt inkonsekventa. Och dessutom kan ” över natten en beställning som inte levereras i taget ” bli ” tio icke levererade beställningar per kund ” eller till och med ” oberoende av kunden, hundra icke levererade beställningar per lager ”, så att aggregatet inte längre kan motiveras.
Svar
Kortversion
Motivet för DDD är att domänobjekt är abstraktioner som ska uppfylla dina funktionella domänkrav – om domänobjekt inte enkelt kan uppfylla dessa krav antyder det att du använder fel abstraktion.
Namngivning Domänobjekt med hjälp av Enhetsnamn kan leda till att dessa föremål blir tätt kopplade till varandra och blir uppblåsta ”gud” -objekt, och de kan kasta upp problem som det i denna fråga som som ”Var är rätt plats att s ut CreateOrder-metoden? ”.
För att göra det lättare att identifiera den ”rätta” aggregerade roten, överväga ett annat tillvägagångssätt där domänobjekt baseras på de funktionella högnivåaffärskraven – dvs.välj substantiv som hänvisar till funktionella krav och / eller beteenden som användare av systemet behöver utföra.
Lång version
DDD är ett tillvägagångssätt för OO Design som är tänkt att resultera i en graf med Domain Objects i Business Lager i ditt system – Domänobjekt är ansvariga för att uppfylla dina höga affärsbehov och bör helst kunna förlita sig på datalagret för saker som prestanda och integritet för det underliggande ihållande datalagret.
Ett annat sätt att titta på det kan vara punktpunkterna i den här listan
- Enhetsbeteckningar föreslår vanligtvis dataattribut.
- Domännamn bör föreslå beteende
- DDD- och OO-modellering handlar om abstraktioner baserade på funktionella krav och kärndomän / affärslogik.
- Business Logic Layer ansvarar för att uppfylla domänkrav på hög nivå
En av de vanligaste missuppfattningarna om DDD är att Domain Objects ska baseras på en viss fysisk realitet världens ”sak” (dvs. något substantiv som du kan peka på i den verkliga världen, tillskriven med alla slags data / egenskaper), men data / attribut för dessa verkliga saker gör inte nödvändigtvis en bra utgångspunkt när du försöker spika ner funktionella krav.
Naturligtvis borde Business Logic använda dessa data, men domänobjekt själva bör i slutändan vara abstraktioner som representerar funktionella domänkrav och beteenden.
Till exempel; substantiv som Order
eller Customer
innebär inte något beteende och är därför i allmänhet ohjälpsamma abstraktioner för att representera affärslogik och domänobjekt.
När du letar efter de typer av abstraktioner som kan vara användbara för att representera affärslogik, överväga typiska krav som du kan förvänta dig att ett system uppfyller:
- Som säljare, jag vill skapa en beställning för en ny kund så att jag kan generera en faktura för de produkter som ska säljas med deras priser och antal.
- Som kundtjänstrådgivare vill jag avbryta en väntande order så att Beställningen fullgörs inte av en lageroperatör.
- Som kundtjänstrådgivare vill jag returnera en orderrad så att produkten kan justeras i inventeringen och betalningen återbetalas via kundens ursprungliga betalning. metod.
- Som lageroperatör vill jag se alla produkter i en väntande beställning och leveransinformation så att jag kan välja produkterna och skicka dem via kuriren.
- etc.
Modellering av domänkrav med en DDD-metod
Baserat på listan ovan, överväg några potentiella domänobjekt ts för ett sådant Order-system:
SalesOrderCheckout PendingOrdersStream WarehouseOrderDespatcher OrderRefundProcessor
Som domänobjekt representerar dessa abstraktioner som äger olika krav på beteendedomän; faktiskt deras substantiv antyder starkt de specifika funktionskrav som de uppfyller.
(Det kan finnas ytterligare infrastruktur där också, till exempel ett EventMediator
att passera aviseringar för observatörer som vill veta när en ny beställning har skapats, eller när en beställning har skickats osv.).
Till exempel, SalesOrderCheckout
behöver antagligen hantera data om kunder, frakt och produkter, men handlar inte om något att göra med beteendet för fraktorder, sortering av väntande order eller utfärdande av återbetalning.
För SalesOrderCheckout
för att uppfylla sina domänkrav inkluderar att tillämpa dessa affärsregler som att förhindra att kunder beställer för många artiklar, eventuellt kör en del validering och kanske höjer meddelanden för andra delar av systemet – det kan göra alla dessa saker utan att nödvändigtvis behöva vara beroende av något av de andra objekten.
DDD använder Entity Nouns för att representera domänobjekt
Ther e är ett antal potentiella faror vid behandling av enkla substantiv som Order
, Customer
och Product
som domänobjekt; bland dessa problem är de du hänvisar till i frågan:
- Om en metod hanterar en
Order
, enCustomer
och ettProduct
, vilket domänobjekt tillhör det? - Var är den sammanlagda roten för dessa 3 objekt?
Om du väljer Enhetsbeteckningar för att representera domänobjekt kan ett antal saker hända:
-
Order
,Customer
ochProduct
riskerar att växa till” gud ”-objekt - Risk för att sluta med en enda
Manager
gudobjekt för att knyta ihop allt. - Dessa objekt riskerar att bli tätt kopplade till varandra – det kan vara svårt att uppfylla dominkraven utan att klara
this
(ellerself
) - En risk att utveckla ”läckande” abstraktioner – dvs.domänobjekt som förväntas exponera dussintals
get
/set
metoder som försvagar inkapsling (eller, om du inte gör det, då någon annan programmerare kommer troligtvis senare …). - Risk för att domänobjekt blir uppblåsta med en komplex blandning av affärsdata (t.ex. användardatainmatning via ett användargränssnitt) och övergående tillstånd (t.ex. en ”historik” om användaråtgärder när ordern har ändrats).
DDD, OO Design och Plain Models
En vanlig missuppfattning angående DDD och OO Design är att ”vanliga” modeller på något sätt är dåligt ”eller” antimönster ”. Martin Fowler skrev en artikel som beskriver Anemic Domain Model – men som han klargör det i artikeln, bör DDD själv inte ”motsäger” tillvägagångssättet för ren separation mellan lager
”Det är också värt att betona att sätta beteende i domänobjekten inte borde motsäga det solida tillvägagångssättet att använda skiktning till sepa bedöm domänlogik från sådana saker som uthållighet och presentationsansvar. Logiken som ska finnas i ett domänobjekt är domänlogik – valideringar, beräkningar, affärsregler – vad du än vill kalla det. ”
Med andra ord, att använda vanliga modeller för att hålla affärsdata som överförs mellan andra lager (t.ex. en ordermodell som skickas in av ett användarprogram när användaren vill skapa en ny order) är inte samma sak som en ”Anemic Domain Model”. ”vanliga” datamodeller är ofta det bästa sättet att spåra data och överföra data mellan lager (som en REST-webbtjänst, en uthållighetsbutik, ett program eller ett användargränssnitt osv.).
Affärslogik kan bearbeta data i dessa modeller och kan spåra dem som en del av affärstillståndet – men kommer inte nödvändigtvis att äga dessa modeller.
Den sammanlagda roten
Titta igen på exemplet Domain Objects – SalesOrderCheckout
, PendingOrdersStream
, WarehouseOrderDespatcher
, OrderRefundProcessor
det finns fortfarande ingen uppenbar aggregerad rot, men det gör det det spelar ingen roll eftersom dessa domänobjekt har ett väldigt separat ansvar som inte verkar överlappa varandra.
Funktionellt finns det inget behov av SalesOrderCheckout
att prata med PendingOrdersStream
eftersom jobbet för den förra är komplett när den har lagt till en ny beställning i databasen. Å andra sidan kan PendingOrdersStream
hämta nya beställningar från databasen. Dessa objekt behöver faktiskt inte interagera med varje andra direkt (kanske en Event Mediator kan ge meddelanden mellan de två, men jag förväntar mig att någon koppling mellan dessa objekt är väldigt lös)
Kanske kommer Aggregate Root att vara en IoC-behållare som injicerar en eller flera av dessa domänobjekt till en UI-kontroller, som också levererar annan infrastruktur som EventMediator
och Repository
. Eller kanske blir det någon form av lätt Orchestrator-tjänst som sitter ovanpå Business Layer.
Den sammanlagda roten behöver inte nödvändigtvis vara ett domänobjekt. För att hålla åtskillnad mellan bekymmer mellan domänobjekt är det i allmänhet bra när det sammanlagda root är ett separat objekt utan affärslogik.
Kommentarer
- Jag nedröstade eftersom ditt svar sammanfattar begrepp från Entity Framework som är en Microsoft-specifik teknik med Domain Driven Design, som kommer från en bok skriven av Eric Evans med samma namn. Du har några uttalanden i ditt svar som står i direkt motsättning med DDD-boken och denna fråga nämner inget om Entity Framework men är specifikt taggad med DDD. Det finns inte heller ’ någon fråga om uthållighet i frågan så jag ser inte ’ var databastabeller är relevanta.
- @RibaldEddie Tack för att du tog dig tid att granska svaret och kommentera, jag instämmer i att omnämnandet av ihållande data inte ’ verkligen behöver vara med i svaret så jag ’ har omformulerat det för att ta bort det. Huvudfokus för svaret kan sammanfattas som ” Enhetsnamn är ofta inte ’ t mycket bra domännamnklassnamn på grund av deras tendens att bli tätt kopplade uppblåsta gudobjekt ”, förhoppningsvis är meddelandet och resonemanget WRT funktionella krav / beteende tydligare nu?
- DDD-boken gör inte ’ t har det konceptet IIRC. Den har enheter, som bara är klasser som när de instienseras har en ihållande och unik identitet så att två separata förekomster antyder två unika och beständiga saker, som står i kontrast till värdeobjekt som inte ’ t har någon identitet och inte ’ t kvarstår över tiden. I boken är både Entities och Value-objekt domänobjekt.
Svar
i vår problemdomän har vi en affärsregel som säger att en kund bara kan ha en beställning som inte levereras åt gången.
Innan du går för djupt ner i kaninhålet bör du granska Greg Young ”s diskussion om ställer in konsistens , och särskilt:
Vilken är affärseffekten av att ha ett misslyckande?
Eftersom i många fall är det rätta svaret inte för att försöka förhindra fel sak händer, men istället för att generera undantagsrapporter när det kan finnas ett problem.
Men förutsatt att flera icke-levererade order är ett betydande ansvar gentemot ditt företag …
Ja, om du vill se till att det bara finns en beställning som inte levereras måste det finnas något aggregat som kan se alla beställningar för en kund .
Det aggregatet är inte nödvändigtvis kundaggregatet .
Det kan vara ungefär som en orderkö eller en orderhistorik där alla beställningar för en viss kunden går in i samma kö. Enligt vad du har sagt behöver det inte alla kundens profildata, så det borde inte vara en del av detta aggregat.
Men när det gäller frakt gör de alla sina åtgärder på beställningen, inte kunden.
Ja, när du faktiskt arbetar med uppfyllande och dra ark, historikvyn är inte särskilt relevant.
Historikvyn, för att genomdriva din invariant, behöver bara order-id och dess aktuella behandlingsstatus. Det behöver inte nödvändigtvis vara en del av samma aggregat som ordern – kom ihåg, aggregerade gränser handlar om att hantera förändring, inte strukturera vyer. , och orderhistoriken som ett separat aggregat och samordnar aktiviteten mellan de två.
Svar
Du har konfigurerat ett stråmänniskaexempel. Det är för förenklat och jag tvivlar på att det återspeglar ett verkligt system. Jag skulle inte modellera dessa enheter och deras relaterade beteende på det sätt som du angav på grund av det.
Dina klasser behöver modellera ordningens tillstånd på ett sätt som återspeglas i flera aggregat. Till exempel när kunden sätter systemet i det tillstånd där kundens beställningsförfrågan måste behandlas kan jag skapa ett aggregat för domänenhetsobjekt som heter CustomerOrderRequest
eller PendingCustomerOrder
eller till och med bara CustomerOrder
, eller vilket språk som företaget använder, och det kan hålla en pekare till både kunden och OrderLines och sedan ha en metod som canCustomerCompleteOrder()
som anropas från servicelagret.
Detta domänobjekt skulle innehålla affärslogiken för att avgöra om ordern var giltig eller inte.
Om ordern var giltig och bearbetad skulle jag ha något sätt att överföra detta objekt till ett annat objekt som representerade den bearbetade ordern.
Jag tror att problemet med din förståelse är att du använder en konstruerad överförenklat exempel på aggregat. Ett PendingOrder
kan vara det egna aggregatet separat från ett UndeliveredOrder
och återigen separat från ett eller en CancelledOrder
eller vad som helst.
Kommentarer
- Även om ditt försök på könsneutralt språk är underhållande, skulle jag notera att kvinnor aldrig står i åkrar för att skrämma bort kråkor.
- @RobertHarvey att ’ är en konstig sak att fokusera på i mitt inlägg. Fågelskrämmor och figurer har båda dykt upp regelbundet i kvinnlig form genom historien.
- Du skulle ’ inte ha gjort skillnaden i ditt inlägg om du inte ’ anser det inte vara viktigt. Som en fråga om lingvistik är termen ” halmman; ” alla reservationer om sexism trumpas nästan säkert av ” vad i helvete talar han om ” faktor som skapats genom att uppfinna din egen term.
- @RobertHarvey om någon vet vilket halm man menar, jag ’ är säker på att de kan ta reda på vad halmpersoner menar om de inte har ’ inte hört den termen. Kan vi fokusera på innehållet i mitt inlägg snälla skriv programvara?
Svar
Vaughn Vernon nämner detta i sin boken ”Implementing Domain-Driven Design” i början av kapitel 7 (Tjänster):
”Ofta är den bästa indikationen på att du ska skapa en tjänst i domänmodellen när operationen du behöver utföra av plats som en metod på ett aggregat eller ett värdeobjekt ”.
Så i det här fallet kan det finnas en domäntjänst som heter ”CreateOrderService” som tar en kundinstans och listan med artiklar för beställningen.
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 } }
Kommentarer
- Kan du snälla förklara mer hur domäntjänster kan hjälpa till att ta itu med samtidighetsproblem i frågan?
Lämna ett svar