Vedvarende DDD-aggregater

1. Oversikt

I denne opplæringen vil vi utforske mulighetene for vedvarende DDD-aggregater ved hjelp av forskjellige teknologier.

2. Introduksjon til aggregater

Et aggregat er en gruppe forretningsobjekter som alltid må være konsistente. Derfor lagrer og oppdaterer vi aggregater som helhet i en transaksjon.

Aggregate er et viktig taktisk mønster i DDD, som bidrar til å opprettholde konsistensen av forretningsobjektene våre. Imidlertid er ideen om aggregat også nyttig utenfor DDD-sammenheng.

Det er mange forretningssaker der dette mønsteret kan komme til nytte. Som en tommelfingerregel bør vi vurdere å bruke aggregater når det er flere objekter som er endret som en del av den samme transaksjonen.

La oss ta en titt på hvordan vi kan bruke dette når vi modellerer et ordrekjøp.

2.1. Innkjøpsordreeksempel

La oss anta at vi vil modellere en innkjøpsordre:

class Order {private Collection orderLines; private Money totalCost; // ...}
klasse OrderLine {privat produktprodukt; privat int mengde; // ...}
klasse Produkt {privat Pengepris; // ...}

Disse klassene danner et enkelt aggregat. Både orderLines og totalkostnad felt av Rekkefølge må alltid være konsistent, altså totalkostnad skal alltid ha verdien lik summen av alle orderLines.

Nå kan vi alle bli fristet til å gjøre alle disse til fullverdige Java Beans. Men legg merke til at det å introdusere enkle getters og settere i Rekkefølge kan lett bryte innkapslingen av modellen vår og bryte forretningsbegrensningene.

La oss se hva som kan gå galt.

2.2. Naivt samlet design

La oss forestille oss hva som kan skje hvis vi bestemte oss for å naivt legge til getters og setters til alle eiendommene på Rekkefølge klasse, inkludert setOrderTotal.

Det er ingenting som forbyr oss å utføre følgende kode:

Ordreordre = ny ordre (); order.setOrderLines (Arrays.asList (orderLine0, orderLine1)); order.setTotalCost (Money.zero (CurrencyUnit.USD)); // dette ser ikke bra ut ...

I denne koden setter vi manuelt totalkostnad eiendom til null, og bryter en viktig forretningsregel. Definitivt, den totale kostnaden bør ikke være null dollar!

Vi trenger en måte å beskytte forretningsreglene våre på. La oss se på hvordan aggregerte røtter kan hjelpe.

2.3. Samlet rot

An samlet rot er en klasse som fungerer som inngangspunkt for vårt aggregat. All forretningsdrift bør gå gjennom roten. På denne måten kan aggregatrotten ta seg av å holde aggregatet i en konsistent tilstand.

Roten er det som bryr seg om alle våre forretningsinvariere.

Og i vårt eksempel, den Rekkefølge klasse er den rette kandidaten for den samlede roten. Vi trenger bare å gjøre noen modifikasjoner for å sikre at aggregatet alltid er konsistent:

class Order {private final Liste orderLines; private Money totalCost; Order (List orderLines) {checkNotNull (orderLines); if (orderLines.isEmpty ()) {kast ny IllegalArgumentException ("Bestillingen må ha minst en ordrelinje"); } this.orderLines = new ArrayList (orderLines); totalCost = calcTotalCost (); } ugyldig addLineItem (OrderLine orderLine) {checkNotNull (orderLine); orderLines.add (orderLine); totalCost = totalCost.plus (orderLine.cost ()); } ugyldig removeLineItem (int linje) {OrderLine fjernetLine = orderLines.remove (linje); totalCost = totalCost.minus (fjernetLine.cost ()); } Money totalCost () {return totalCost; } // ...}

Å bruke en samlet rot lar oss nå lettere svinge Produkt og OrderLine til uforanderlige gjenstander, der alle egenskapene er endelige.

Som vi kan se, er dette et ganske enkelt aggregat.

Og vi kunne bare ha beregnet den totale kostnaden hver gang uten å bruke et felt.

Akkurat nå snakker vi bare om samlet utholdenhet, ikke samlet design. Følg med, da dette spesifikke domenet vil være nyttig om et øyeblikk.

Hvor godt spiller dette med utholdenhetsteknologier? La oss ta en titt. Til slutt vil dette hjelpe oss med å velge riktig utholdenhetsverktøy for vårt neste prosjekt.

3. JPA og dvalemodus

I denne delen, la oss prøve å fortsette Rekkefølge aggregat ved hjelp av JPA og Hibernate. Vi bruker Spring Boot og JPA starter:

 org.springframework.boot spring-boot-starter-data-jpa 

For de fleste av oss ser dette ut til å være det mest naturlige valget. Vi har tross alt brukt mange år på å jobbe med relasjonelle systemer, og vi kjenner alle populære ORM-rammer.

Sannsynligvis det største problemet når vi arbeider med ORM-rammeverk, er forenkling av modelldesignet vårt. Det er også noen ganger referert til som Objekt-relasjonell impedans mismatch. La oss tenke på hva som ville skje hvis vi ønsket å fortsette vårt Rekkefølge samlet:

@DisplayName ("gitt ordre med to ordrelinjer, når vedvarende, så blir ordre lagret") @Test offentlig ugyldig test () kaster Unntak {// gitt JpaOrder rekkefølge = prepareTestOrderWithTwoLineItems (); // når JpaOrder lagretOrder = repository.save (ordre); // så fant JpaOrderOrder = repository.findById (savedOrder.getId ()) .get (); assertThat (foundOrder.getOrderLines ()). har størrelse (2); }

På dette tidspunktet vil denne testen kaste et unntak: java.lang.IllegalArgumentException: Ukjent enhet: com.baeldung.ddd.order.Order. Åpenbart mangler vi noen av JPA-kravene:

  1. Legg til kartleggingsanmerkninger
  2. OrderLine og Produkt klasser må være enheter eller @Embeddable klasser, ikke enkle verdiobjekter
  3. Legg til en tom konstruktør for hver enhet eller @Embeddable klasse
  4. Erstatte Penger eiendommer med enkle typer

Hmm, vi må endre utformingen av Rekkefølge samlet for å kunne bruke JPA. Selv om det ikke er noen stor sak å legge til merknader, kan de andre kravene gi mange problemer.

3.1. Endringer i verdiobjektene

Det første problemet med å prøve å tilpasse et aggregat i JPA er at vi trenger å bryte utformingen av våre verdiobjekter: Egenskapene deres kan ikke lenger være endelige, og vi trenger å bryte innkapslingen.

Vi trenger å legge kunstige ID-er til OrderLine og Produkt, selv om disse klassene aldri ble designet for å ha identifikatorer. Vi ønsket at de skulle være enkle verdiobjekter.

Det er mulig å bruke @En del av og @ElementCollection merknader i stedet, men denne tilnærmingen kan komplisere ting mye når du bruker en kompleks objektgraf (for eksempel @Embeddable objektet har en annen @En del av eiendom osv.).

Ved hjelp av @En del av kommentar legger ganske enkelt til flate egenskaper i overordnet tabell. Bortsett fra det, grunnleggende egenskaper (f.eks. Av String type) krever fortsatt en settermetode, som bryter med ønsket verdi design.

Tomt konstruktørkrav tvinger verdien av objektegenskapene til å ikke være endelige lenger, og bryter et viktig aspekt av vår originale design. Sannheten blir fortalt at dvalemodus kan bruke den private no-args-konstruktøren, noe som reduserer problemet litt, men det er fortsatt langt fra å være perfekt.

Selv når vi bruker en privat standardkonstruktør, kan vi enten ikke merke egenskapene våre som endelige, eller vi må initialisere dem med standardverdier (ofte null) i standardkonstruktøren.

Imidlertid, hvis vi ønsker å være fullt JPA-kompatible, må vi bruke minst beskyttet synlighet for standardkonstruktøren, noe som betyr at andre klasser i samme pakke kan opprette verdiobjekter uten å spesifisere verdier for egenskapene deres.

3.2. Komplekse typer

Dessverre kan vi ikke forvente at JPA automatisk kartlegger tredjeparts komplekse typer i tabeller. Bare se hvor mange endringer vi måtte innføre i forrige avsnitt!

For eksempel når du jobber med vår Rekkefølge samlet, vil vi støte på vedvarende vanskeligheter Joda Money Enger.

I et slikt tilfelle kan vi ende opp med å skrive tilpasset type @Konverterer tilgjengelig fra JPA 2.1. Det kan imidlertid kreve litt ekstra arbeid.

Alternativt kan vi også dele Penger eiendom i to grunnleggende egenskaper. For eksempel String for valutaenhet og BigDecimal for den faktiske verdien.

Mens vi kan skjule implementeringsdetaljene og fortsatt bruke Penger klasse gjennom den offentlige metoden API, viser praksisen at de fleste utviklere ikke kan rettferdiggjøre ekstraarbeidet, og degenererer ganske enkelt modellen for å være i samsvar med JPA-spesifikasjonen i stedet.

3.3. Konklusjon

Selv om JPA er en av de mest adopterte spesifikasjonene i verden, er det kanskje ikke det beste alternativet for å vedvare våre Rekkefølge samlet.

Hvis vi vil at modellen vår skal gjenspeile de virkelige forretningsreglene, bør vi designe den for ikke å være en enkel 1: 1-fremstilling av de underliggende tabellene.

I utgangspunktet har vi tre alternativer her:

  1. Lag et sett med enkle dataklasser og bruk dem til å vedvare og gjenskape den rike forretningsmodellen. Dessverre kan dette kreve mye ekstra arbeid.
  2. Godta begrensningene til JPA og velg riktig kompromiss.
  3. Vurder en annen teknologi.

Det første alternativet har det største potensialet. I praksis er de fleste prosjekter utviklet ved hjelp av det andre alternativet.

La oss nå vurdere en annen teknologi for å vedvare aggregater.

4. Dokumentbutikk

En dokumentbutikk er en alternativ måte å lagre data på. I stedet for å bruke relasjoner og tabeller, lagrer vi hele objekter. Dette gjør en dokumentbutikk til en potensielt perfekt kandidat for vedvarende aggregater.

For behovene i denne opplæringen, vil vi fokusere på JSON-lignende dokumenter.

La oss se nærmere på hvordan bestillingsproblemet vårt ser ut i en dokumentbutikk som MongoDB.

4.1. Vedvarende aggregering ved bruk av MongoDB

Nå er det ganske mange databaser som kan lagre JSON-data, en av de populære er MongoDB. MongoDB lagrer faktisk BSON, eller JSON i binær form.

Takket være MongoDB kan vi lagre Rekkefølge eksempel aggregat som den er.

Før vi går videre, la oss legge til Spring Boot MongoDB starter:

 org.springframework.boot spring-boot-starter-data-mongodb 

Nå kan vi kjøre en lignende testtilfelle som i JPA-eksemplet, men denne gangen ved hjelp av MongoDB:

@DisplayName ("gitt ordre med to ordrelinjer, når vedvarende bruker mongo repository, så blir ordren lagret") @Test ugyldig test () kaster Unntak {// gitt Order order = preparTestOrderWithTwoLineItems (); // når repo.save (ordre); // deretter Liste funnet Bestillinger = repo.findAll (); assertThat (funnetBestillinger) .hasSize (1); Liste funnetOrderLines = funnetOrders.iterator () .neste () .getOrderLines (); assertThat (foundOrderLines) .hasSize (2); assertThat (foundOrderLines) .containsOnlyElementsOf (order.getOrderLines ()); }

Det som er viktig - vi endret ikke originalen Rekkefølge samlede klasser i det hele tatt; ikke behov for å lage standardkonstruktører, settere eller tilpasset omformer for Penger klasse.

Og her er hva vår Rekkefølge aggregat vises i butikken:

{"_id": ObjectId ("5bd8535c81c04529f54acd14"), "orderLines": [{"product": {"price": {"money": {"currency": {"code": "USD", "numericCode": 840, "decimalPlaces": 2}, "amount": "10.00"}}}, "amount": 2}, {"product": {"price": {"money": {"currency": {"code ":" USD "," numericCode ": 840," decimalPlaces ": 2}," amount ":" 5.00 "}}}," amount ": 10}]," totalCost ": {" money ": {" currency ": {" code ":" USD "," numericCode ": 840," decimalPlaces ": 2}," amount ":" 70.00 "}}," _class ":" com.baeldung.ddd.order.mongo.Order "}

Dette enkle BSON-dokumentet inneholder helheten Rekkefølge samlet i ett stykke, som passer perfekt til vår opprinnelige forestilling om at alt dette skal være i samsvar.

Merk at komplekse objekter i BSON-dokumentet rett og slett serieres som et sett med vanlige JSON-egenskaper. Takket være dette, til og med tredjepartsklasser (som Joda Money) kan enkelt serialiseres uten behov for å forenkle modellen.

4.2. Konklusjon

Vedvarende aggregater ved hjelp av MongoDB er enklere enn å bruke JPA.

Dette betyr absolutt ikke at MongoDB er overlegen tradisjonelle databaser. Det er mange legitime tilfeller der vi ikke engang skal prøve å modellere klassene våre som aggregater og i stedet bruke en SQL-database.

Likevel, når vi har identifisert en gruppe objekter som alltid skal være konsistente i henhold til de komplekse kravene, kan bruk av en dokumentbutikk være et veldig tiltalende alternativ.

5. Konklusjon

I DDD inneholder aggregater vanligvis de mest komplekse objektene i systemet. Å jobbe med dem trenger en helt annen tilnærming enn i de fleste CRUD-applikasjoner.

Bruk av populære ORM-løsninger kan føre til en forenklet eller overeksponert domenemodell, som ofte ikke er i stand til å uttrykke eller håndheve intrikate forretningsregler.

Dokumentbutikker kan gjøre det lettere å vedvare aggregater uten å ofre modellens kompleksitet.

Den fullstendige kildekoden for alle eksemplene er tilgjengelig på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found