Find DDD Aggregate Root
On februar 1, 2021 by adminLad os spille alles yndlingsspil, find Aggregrate Root. Lad os bruge det kanoniske domæne for kunde / ordre / ordrelinjer / produktproblemer. Traditionelt er kunde, ordre og produkt ARer, hvor ordrelinjer er enheder under ordren. Logikken bag dette er, at du skal identificere kunder, ordrer og produkter, men en OrderLine ville ikke eksistere uden en ordre. Så i vores problemdomæne har vi en forretningsregel, der siger, at en kunde kun kan have en ikke-leveret ordre ad gangen.
Flytter det ordren under kundens samlede rod? Det tror jeg, det gør. Men på den måde gør det kunden AR ret stor og underlagt problemer med samtidighed senere.
Eller hvad hvis vi havde en forretningsregel om, at en kunde kun kan bestille et bestemt produkt en gang i sin levetid. Dette er mere bevis, der kræver, at kunden ejer ordren.
Men når det kommer til forsendelse gør de alle deres handlinger i forhold til ordren, ikke kunden. Det er lidt dumt at skulle indlæse hele kunden for at markere en individuel ordre som leveret.
Dette er hvad jeg foreslå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ørste bekymring er samtidighedsproblemet og det faktum, at selve ordenen har karakteristiske s af en samlet rod.
Svar
Hvad er kriterierne for at definere et aggregat?
Lad os gå tilbage til det grundlæggende i den store blå bog:
Samlet: En klynge af tilknyttede objekter, der behandles som en enhed med henblik på dataændringer . Eksterne referencer er begrænset til et medlem af AGGREGATE, der er udpeget som roden. Et sæt af konsistensregler gælder inden for AGGREGATEs grænser.
Målet er at opretholde invarianterne. Men det er også at styre korrekt lokal identitet, dvs. identificere objekter, der ikke har en mening alene.
Order
og Order line
tilhører endeligt en sådan klynge. For eksempel:
- Slet en
Order
, kræver sletning af alle dens linjer. - Sletning af en linje kan kræve, at de følgende linjer omnummereres
- Tilføjelse af en ny linje vil kræve, at linjemålet bestemmes ud fra alle de andre linjer i samme rækkefølge.
- Ændring af nogle ordreoplysninger, som f.eks. valutaen, kan påvirke betydningen af prisen i linjeposterne (eller kræve at genberegne priserne).
Så her er det samlede aggregat kræves for at sikre konsistensregler og invarianter.
Hvornår skal man stoppe?
Nu beskriver du nogle forretningsregler og argumenterer for at du skal tage kunden i betragtning for at sikre dem som en del af aggregatet:
Vi har en forretning s regel om, at en kunde kun kan have en ikke-leveret ordre ad gangen.
Selvfølgelig, hvorfor ikke. Lad os se konsekvenserne: ordren vil altid være tilgængelig via kunden. Er dette det virkelige liv? Når medarbejderne udfylder felterne for at levere ordren, skal de læse kundens stregkode og ordrestregkoden for at få adgang til ordren ? Faktisk generelt er en ordres identitet global, ikke lokal for en kunde, og denne relative uafhængighed antyder at holde ham uden for aggregatet.
Desuden ser disse forretningsregler mere ud som politikker: det er en vilkårlig beslutning fra virksomheden at køre deres proces med disse regler. Hvis reglerne ikke overholdes, kan chefen muligvis være utilfreds, men dataene er ikke rigtig inkonsekvente. Og desuden kunne ” natten over en ikke-leveret ordre ad gangen ” kunne blive ” ti ikke-leverede ordrer pr. kunde ” eller endda ” uafhængigt af kunden, hundrede ikke-leverede ordrer pr. lager “, så aggregatet muligvis ikke længere er berettiget.
Svar
Kort version
Begrundelsen for DDD er, at domæneobjekter er abstraktioner, der skal opfylde dine funktionelle domænekrav – hvis domæneobjekter ikke er i stand til let at opfylde disse krav, antyder det, at du muligvis bruger den forkerte abstraktion.
Navngivning Domæneobjekter ved hjælp af Entitetsnavne kan føre til, at disse objekter bliver tæt koblet med hinanden og bliver oppustede “gud” -objekter, og de kan smide problemer som den i dette spørgsmål, såsom som “Hvor er det rigtige sted at s ud af CreateOrder-metoden? “.
For at gøre det lettere at identificere den “rigtige” samlede rod skal du overveje en anden tilgang, hvor domæneobjekter er baseret på de funktionelle forretningskrav på højt niveau – dvs.vælge navneord, der henviser til funktionelle krav og / eller adfærd, som brugerne af systemet skal udføre.
Lang version
DDD er en tilgang til OO Design, som er beregnet til at resultere i en graf med Domain Objects i Business Lag af dit system – Domæneobjekter er ansvarlige for at opfylde dine forretningskrav på højt niveau og bør ideelt set være i stand til at stole på datalaget til ting som ydeevnen og integriteten af det underliggende vedvarende datalager.
En anden måde at se på det kan være punkttegnene på denne liste
- Enhedsnavne foreslår typisk dataegenskaber.
- Domænenavne bør foreslå adfærd
- DDD og OO Modellering beskæftiger sig med abstraktioner baseret på funktionelle krav og kernedomæne / forretningslogik.
- Business Logic Layer er ansvarlig for at opfylde domænekrav på højt niveau
En af de almindelige misforståelser vedrørende DDD er, at domæneobjekter skal baseres på en vis fysisk virkelighed verdens “ting” (dvs. et substantiv, som du kan pege på i den virkelige verden, tilskrevet med alle slags data / egenskaber), men data / attributterne for disse ting i den virkelige verden er ikke nødvendigvis et godt udgangspunkt, når du prøver at sømme ned funktionelle krav.
Selvfølgelig skal Business Logic bruge disse data, men domæneobjekter i sig selv skal i sidste ende være abstraktioner, der repræsenterer funktionelle domæne krav og adfærd.
For eksempel; substantiver såsom Order
eller Customer
indebærer ingen adfærd og er derfor generelt uhensigtsmæssige abstraktioner til at repræsentere forretningslogik og domæneobjekter.
Når du leder efter de former for abstraktioner, der kan være nyttige til at repræsentere Business Logic, skal du overveje typiske krav, som du måske forventer, at et system skal opfylde:
- Som sælger ønsker at oprette en ordre for en ny kunde, så jeg kan generere en faktura for de produkter, der skal sælges med deres priser og mængde.
- Som kundeserviceadviseur vil jeg annullere en afventende ordre, så Ordren er ikke opfyldt af en lageroperatør.
- Som kundeserviceadviseur vil jeg returnere en ordrelinje, så Produktet kan justeres i lagerbeholdningen, og betaling refunderes via kundens originale betaling. metode.
- Som lageroperatør vil jeg se alle produkter i en afventende ordre og forsendelsesoplysningerne, så jeg kan vælge produkterne og sende dem via kurer.
- osv.
Modellering af domænskrav med en DDD-tilgang
Baseret på ovenstående liste, overvej nogle potentielle domæneobjekter ts for et sådant ordresystem:
SalesOrderCheckout PendingOrdersStream WarehouseOrderDespatcher OrderRefundProcessor
Som domæneobjekter repræsenterer disse abstraktioner, som tager ejerskab af forskellige krav til adfærdsmæssige domæner; faktisk deres navneord antyder stærkt det specifikke funktionelle krav, de opfylder.
(Der kan også være yderligere infrastruktur derinde, f.eks. et EventMediator
, der skal passeres meddelelser til observatører, der ønsker at vide, hvornår en ny ordre er oprettet, eller når en ordre er afsendt osv.).
For eksempel skal SalesOrderCheckout
sandsynligvis håndtere data om kunder, forsendelse og produkter, men er ikke involveret i noget, der har at gøre med adfærd for forsendelsesordrer, sortering af ventende ordrer eller udstedelse af refusioner.
For SalesOrderCheckout
for at opfylde sine domænekrav inkluderer håndhævelse af disse forretningsregler, såsom at forhindre kunder, der bestiller for mange varer, muligvis kører en vis validering og måske hæve underretninger til andre dele af systemet – det kan gøre alle disse ting uden nødvendigvis at skulle afhænge af ethvert af de andre objekter.
DDD ved hjælp af enhedsnavne til at repræsentere domæneobjekter
Ther e er et antal potentielle farer ved behandling af enkle navneord som Order
, Customer
og Product
som domæneobjekter; blandt disse problemer er dem, du henviser til i spørgsmålet:
- Hvis en metode håndterer en
Order
, enCustomer
og etProduct
, hvilket domæneobjekt tilhører det? - Hvor er den samlede rod for disse 3 objekter?
Hvis du vælger enhedsnavne til at repræsentere domæneobjekter, kan der ske en række ting:
-
Order
,Customer
ogProduct
risikerer at vokse til” gud “-objekter - Risiko for at ende med en enkelt
Manager
gud-objekt for at binde alt sammen. - Disse objekter risikerer at blive tæt koblet til hinanden – det kan være svært at opfylde domæne krav uden at passere
this
(ellerself
) - En risiko for at udvikle “utætte” abstraktioner – dvs.domæneobjekter, der forventes at udsætte snesevis af
get
/set
metoder, der svækker indkapsling (eller hvis du ikke gør det, så en anden programmør sandsynligvis senere ..). - En risiko for, at domæneobjekter bliver oppustede med en kompleks blanding af forretningsdata (f.eks. brugerdataindgang via et brugergrænseflade) og midlertidig tilstand (f.eks. en “historie” af brugerhandlinger når ordren er ændret).
DDD-, OO-design og almindelige modeller
En almindelig misforståelse vedrørende DDD- og OO-design er, at “almindelige” modeller på en eller anden måde er ” dårlig “eller et” anti-mønster “. Martin Fowler skrev en artikel, der beskriver Anemic Domain Model – men som han gør det klart i artiklen, bør DDD selv ikke “modsiger” tilgangen til ren adskillelse mellem lag
“Det er også værd at understrege, at det at placere adfærd i domæneobjekterne ikke bør modsige den solide tilgang til at bruge lagdeling til sepa Bedøm domænelogik fra ting som vedholdenheds- og præsentationsansvar. Den logik, der skal være i et domæneobjekt, er domænelogik – valideringer, beregninger, forretningsregler – uanset hvad du vil kalde det. “
Med andre ord er det ikke det samme som at bruge “almindelige modeller, der overføres mellem andre lag (f.eks. En ordremodel, der sendes ind af en brugerapplikation, når brugeren ønsker at oprette en ny ordre”). “almindelige” datamodeller er ofte den bedste måde at spore data og overføre data mellem lag (såsom en REST-webtjeneste, en persistensbutik, en applikation eller et brugergrænseflade osv.).
Forretningslogik behandler muligvis data i disse modeller og kan spore dem som en del af forretningstilstanden – men vil ikke nødvendigvis tage ejerskab af disse modeller.
Den samlede rod
Ser igen på eksemplet Domain Objects – SalesOrderCheckout
, PendingOrdersStream
, WarehouseOrderDespatcher
, OrderRefundProcessor
der er stadig ingen indlysende samlet rod, men det gør det det betyder faktisk ikke noget, fordi disse domæneobjekter har meget forskellige ansvarsområder, som ikke synes at overlappe hinanden.
Funktionelt er der ikke noget behov for SalesOrderCheckout
for at tale med PendingOrdersStream
fordi jobbet for den tidligere er komplet, når den har tilføjet en ny ordre til databasen. På den anden side kan PendingOrdersStream
hente nye ordrer fra databasen. Disse objekter behøver faktisk ikke at interagere med hver andre direkte (Måske giver en begivenhedsformidler meddelelser mellem de to, men jeg forventer, at enhver kobling mellem disse objekter er meget løs)
Måske vil den samlede rod være en IoC-beholder, der injicerer en eller flere af disse domæneobjekter til en UI-controller, der også leverer anden infrastruktur som EventMediator
og Repository
. Eller måske vil det være en slags letvægts Orchestrator Service, der sidder oven på Business Layer.
Den samlede rod behøver ikke nødvendigvis at være et domæneobjekt. Af hensyn til at opretholde adskillelse af bekymringer mellem domæneobjekter er det generelt en god ting, når det samlede root er et separat objekt uden forretningslogik.
Kommentarer
- Jeg nedstemte, fordi dit svar samler koncepter fra Entity Framework, som er en Microsoft-specifik teknologi med Domain Driven Design, som er fra en bog skrevet af Eric Evans med samme navn. Du har nogle udsagn i dit svar, der er i direkte modstrid med DDD-bogen, og dette spørgsmål nævner nul enheden om Entity Framework, men er specifikt tagget med DDD. Der ‘ nævnes slet ikke noget vedholdende i spørgsmålet, så jeg ser ikke ‘ hvor databasetabeller er relevante.
- @RibaldEddie Tak fordi du tog dig tid til at gennemgå svaret og kommentere, jeg er enig i at nævne vedvarende data ikke ‘ behøver ikke at være i svaret, så jeg ‘ har omformuleret det for at fjerne det. Hovedfokus for svaret kunne sammenfattes som ” Entitetsnavn er ofte ikke ‘ t meget gode domæneobjektklassenavne på grund af deres tendens til blive tæt sammenkoblede oppustede gudobjekter “, forhåbentlig er meddelelsen og ræsonnementet WRT-funktionelle krav / adfærd klarere nu?
- DDD-bogen gør ikke ‘ t har dette begreb IIRC. Den har enheder, der kun er klasser, der, når de instienseres, har en vedvarende og unik identitet, så to adskilte forekomster indebærer to unikke og vedvarende i stand ting, der står i kontrast til værdiobjekter, der ikke ‘ t har nogen identitet og don ‘ t vedvarer over tid. I bogen er både enheder og værdiobjekter domæneobjekter.
Svar
i vores problemdomæne har vi en forretningsregel, der siger, at en kunde kun kan have en ikke-leveret ordre ad gangen.
Før du går for dybt ned i kaninhullet, skal du gennemse Greg Unge diskussion af indstil konsistens , og især:
Hvad er den forretningsmæssige indvirkning ved at have en fiasko?
Fordi i mange tilfælde er det rigtige svar ikke for at forsøge at forhindre den forkerte ting sker, men i stedet for at generere undtagelsesrapporter når der kan være et problem.
Men forudsat at flere ikke-leverede ordrer er et væsentligt ansvar over for din virksomhed ….
Ja, hvis du vil sikre dig, at der kun er en ikke-leveret ordre, skal der være noget samlet, der kan se alle ordrer til en kunde .
Dette aggregat er ikke nødvendigvis kundeaggregatet .
Det kan være noget i retning af en ordrekø eller en ordrehistorik, hvor alle ordrer til en bestemt kunden går i samme kø. Fra hvad du har sagt, behøver det ikke alle kundens profildata, så det skal ikke være en del af dette aggregat.
Men når det kommer til forsendelse, udfører de alle deres handlinger på ordren, ikke kunden.
Ja, når du rent faktisk arbejder med opfyldelse og træk ark, historikvisningen er ikke særlig relevant.
Historikvisningen, for at håndhæve din invariant, behøver kun ordre-idet og dets aktuelle behandlingsstatus. Det behøver ikke nødvendigvis at være en del af det samme aggregat som ordren – husk, samlede grænser handler om at styre ændringer, ikke strukturere visninger.
Så det kan være, at du håndterer ordren som et samlet , og ordrehistorikken som et separat aggregat og koordinere aktiviteten mellem de to.
Svar
Du har oprettet et eksempel på et halmperson. Det er for simpelt, og jeg tvivler på, at det afspejler et virkeligt system. Jeg ville ikke modellere disse enheder og deres relaterede opførsel på den måde, du specificerede på grund af det.
Dine klasser skal modellere en ordres tilstand på en måde, der afspejles i flere aggregater. For eksempel når kunden sætter systemet i den tilstand, hvor kundens ordreanmodning skal behandles, kan jeg oprette et domæneenhedsobjektaggregat kaldet CustomerOrderRequest
eller PendingCustomerOrder
eller endda bare CustomerOrder
eller et hvilket som helst sprog virksomheden bruger, og det kan holde en markør til både kunden og ordrelinjerne og derefter have en metode som canCustomerCompleteOrder()
som kaldes fra servicelaget.
Dette domæneobjekt indeholder forretningslogikken for at afgøre, om ordren var gyldig.
Hvis ordren var gyldig og behandlet, ville jeg have en eller anden måde at overføre dette objekt til et andet objekt, der repræsenterede den behandlede ordre.
Jeg tror, problemet med din forståelse er, at du bruger en konstrueret overforenklet eksempel på aggregater. Et PendingOrder
kan være det eget aggregat adskilt fra et UndeliveredOrder
og igen adskilt fra et eller en CancelledOrder
eller hvad som helst.
Kommentarer
- Selvom dit forsøg på kønsneutralt sprog er morsomt, vil jeg bemærke, at kvinder aldrig står på marken for at skræmme krager.
- @RobertHarvey at ‘ er en underlig ting at fokusere på i mit indlæg. Fugleskræmsel og figurer begge har regelmæssigt dukket op i kvindelig form gennem historien.
- Du ville ikke ‘ ikke have skelnet i dit indlæg, hvis du ikke ‘ t anser det for vigtigt. Som et spørgsmål om lingvistik er udtrykket ” halm mand; ” eventuelle forbehold over for sexisme er næsten helt sikkert trumfede af ” hvad i helvede taler han om ” faktor skabt ved at opfinde dit eget udtryk.
- @RobertHarvey hvis nogen ved, hvad halm mand betyder, jeg ‘ er sikker på, at de kan finde ud af, hvad halm person mener, hvis de ikke har ‘ ikke hørt det udtryk. Kan vi fokusere på substansen i mit indlæg, vær venlig med software?
Svar
Vaughn Vernon nævner dette i sin bog “Implementing Domain-Driven Design” i begyndelsen af kapitel 7 (Services):
“Ofte er den bedste indikation på, at du skal oprette en tjeneste i domænemodellen, når den operation, du har brug for at udføre, føles ude af sted som en metode på et samlet eller et værdiobjekt “.
Så i dette tilfælde kunne der være en domænetjeneste kaldet “CreateOrderService”, der tager en kundeinstans og listen over varer til ordren.
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 venligst forklare mere, hvordan domænetjeneste kan hjælpe med at tackle samtidige bekymringer i spørgsmålet?
Skriv et svar