DDD összesített adatok

1. Áttekintés

Ebben az oktatóanyagban megvizsgáljuk a DDD-aggregátumok fenntartásának lehetőségeit különböző technológiák segítségével.

2. Bevezetés az összesített adatokba

Az aggregátum üzleti objektumok csoportja, amelyeknek mindig konzisztenseknek kell lenniük. Ezért az összesítéseket egy tranzakción belül mentjük és frissítjük.

Az aggregátum a DDD fontos taktikai mintája, amely segít megőrizni üzleti objektumaink konzisztenciáját. Az aggregátum ötlete azonban a DDD kontextusán kívül is hasznos.

Számos olyan üzleti eset van, ahol ez a minta jól jöhet. Alapszabályként fontolóra kell venni az összesítők használatát, ha több objektum változik ugyanazon tranzakció részeként.

Vizsgáljuk meg, hogyan alkalmazhatnánk ezt a megrendelés modellezésénél.

2.1. Példa megrendelésre

Tehát tegyük fel, hogy egy megrendelést szeretnénk modellezni:

osztály Rendelés {magángyűjtemény orderLines; magánpénz totalCost; // ...}
osztály OrderLine {magántermék termék; privát int mennyiség; // ...}
osztály Termék {magán pénz ára; // ...}

Ezek az osztályok egyszerű összesítést alkotnak. Mindkét orderLines és összköltsége mezői Rendelés mindig következetesnek kell lennie, vagyis összköltsége értékének mindig meg kell egyeznie az összes összegével orderLines.

Most mindannyian megkísérelhetjük, hogy mindezeket teljes értékű Java-babokká változtassuk. De vegye figyelembe, hogy egyszerű getterek és beállítók bevezetése Rendelés könnyen megtörheti modellünk beágyazódását és megsértheti az üzleti kényszereket.

Nézzük meg, mi romolhat el.

2.2. Naiv összesített tervezés

Képzeljük el, mi történhet, ha úgy döntünk, hogy naív módon hozzáadunk betéteket és beállítókat az összes tulajdonsághoz Rendelés osztály, beleértve setOrderTotal.

Semmi sem tiltja a következő kód futtatását:

Megrendelés = új Megrendelés (); order.setOrderLines (Arrays.asList (orderLine0, orderLine1)); order.setTotalCost (Money.zero (CurrencyUnit.USD)); // ez nem néz ki jól ...

Ebben a kódban kézzel állítottuk be a összköltsége vagyon nullára, megsértve egy fontos üzleti szabályt. Mindenképpen a teljes költség nem lehet nulla dollár!

Szükségünk van arra, hogy megvédjük üzleti szabályainkat. Nézzük meg, hogyan segíthetnek az összesített gyökerek.

2.3. Összesített gyökér

An összesített gyökér olyan osztály, amely belépési pontként működik az összesítésünkben. Minden üzleti műveletnek a gyökéren kell keresztül mennie. Így az összesített gyökér gondoskodhat az aggregátum állandó állapotban tartásáról.

A gyökér az, ami minden üzleti invariánsunkat érdekli.

Példánkban pedig a Rendelés osztály az összesített gyökér megfelelő jelöltje. Csak néhány módosítást kell végrehajtanunk annak biztosítása érdekében, hogy az összesítés mindig következetes legyen:

osztály Rendelés {private final List orderLines; magánpénz totalCost; Order (List orderLines) {checkNotNull (orderLines); if (orderLines.isEmpty ()) {dobjon új IllegalArgumentException-t ("A megrendelésnek legalább egy rendeléssorral kell rendelkeznie"); } this.orderLines = new ArrayList (orderLines); totalCost = calcTotalCost (); } void addLineItem (OrderLine orderLine) {checkNotNull (orderLine); orderLines.add (orderLine); totalCost = totalCost.plus (orderLine.cost ()); } void removeLineItem (int sor) {OrderLine removedLine = orderLines.remove (sor); totalCost = totalCost.minus (eltávolítvaLine.cost ()); } Pénz totalCost () {return totalCost; } // ...}

Az összesített gyökér használata lehetővé teszi számunkra, hogy könnyebben fordulhassunk Termék és OrderLine változhatatlan tárgyakká, ahol az összes tulajdonság végleges.

Mint láthatjuk, ez egy nagyon egyszerű összesítés.

És egyszerűen kiszámolhatnánk a teljes költséget minden alkalommal, mező használata nélkül.

Azonban jelenleg csak az aggregátum tartósságáról beszélünk, nem az aggregátum tervezéséről. Maradj velünk, mert ez a konkrét domain egy pillanat alatt jól jön.

Mennyire játszik jól ez a perzisztencia technológiákkal? Lássuk. Végső soron ez segít kiválasztani a következő projektünkhöz a megfelelő perzisztencia eszközt.

3. JPA és Hibernate

Ebben a részben próbáljuk meg kitartani Rendelés összesítés a JPA és a Hibernate használatával. A Spring Boot és a JPA startert fogjuk használni:

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

Legtöbbünk számára ez tűnik a legtermészetesebb választásnak. Végül is éveket töltöttünk a relációs rendszerekkel, és mindannyian ismerjük a népszerű ORM keretrendszereket.

Valószínűleg a legnagyobb probléma az ORM keretrendszerekkel való munka során a modelltervezésünk egyszerűsítése. Néha objektum-relációs impedancia eltérésnek is nevezik. Gondoljuk át, mi történne, ha kitartanánk a mi mellettünk Rendelés összesítés:

@DisplayName ("adott sorrend két sorral, ha fennmarad, akkor a sorrend mentésre kerül") @Test public void test () dobja a Kivételt {// adott JpaOrder parancs = PreparTestOrderWithTwoLineItems (); // amikor a JpaOrder savedOrder = repository.save (order); // akkor a JpaOrder foundOrder = repository.findById (savedOrder.getId ()) .get (); assertThat (foundOrder.getOrderLines ()). hasSize (2); }

Ezen a ponton ez a teszt kivételt jelent: java.lang.IllegalArgumentException: Ismeretlen entitás: com.baeldung.ddd.order.Order. Nyilvánvaló, hogy hiányolunk néhány JPA-követelményt:

  1. Adjon hozzá hozzárendelési kommentárokat
  2. OrderLine és Termék osztályoknak entitásoknak vagy @ Beágyazható osztályok, nem egyszerű értékű objektumok
  3. Adjon hozzá egy üres konstruktort minden entitáshoz, vagy @ Beágyazható osztály
  4. Cserélje ki Pénz tulajdonságok egyszerű típusokkal

Hmm, módosítanunk kell a tervét Rendelés összesíteni, hogy használni lehessen a JPA-t. Bár a kommentárok hozzáadása nem nagy probléma, a többi követelmény rengeteg problémát okozhat.

3.1. Az Értékobjektumok változásai

Az első kérdés, hogy megpróbálunk egy aggregátumot beilleszteni a JPA-ba, az, hogy meg kell szakítanunk értékobjektumaink tervét: Tulajdonságaik már nem lehetnek véglegesek, és meg kell szakítanunk a beágyazást.

Mesterséges azonosítókat kell hozzáadnunk a OrderLine és Termék, még akkor is, ha ezeket az osztályokat soha nem tervezték azonosítókra. Azt akartuk, hogy egyszerű értéktárgyak legyenek.

Használható @Beágyazott és @ElementCollection annotációk helyett, de ez a megközelítés nagyon bonyolulttá teheti a dolgokat, ha egy komplex objektumdiagramot használunk (például @ Beágyazható egy másik objektum @Beágyazott vagyon stb.).

Használata @Beágyazott az annotáció egyszerűen hozzáadja a lapos tulajdonságokat a szülő táblához. Ettől eltekintve alapvető tulajdonságok (pl Húr type) továbbra is szetter módszert igényelnek, amely sérti a kívánt érték objektumtervezést.

Az üres kivitelezői követelmény arra kényszeríti az értékobjektum tulajdonságait, hogy azok már ne legyenek véglegesek, ami megsérti az eredeti tervezésünk fontos szempontját. Igazság szerint a Hibernate használhatja a privát no-args konstruktort, amely kissé enyhíti a problémát, de még mindig korántsem tökéletes.

Még akkor is, ha privát alapértelmezett konstruktort használunk, vagy nem jelölhetjük meg végleges tulajdonságainkat, vagy pedig inicializálnunk kell őket az alapértelmezett konstruktoron belüli alapértelmezett (gyakran null) értékekkel.

Ha azonban teljes mértékben JPA-kompatibilisek akarunk lenni, akkor az alapértelmezett konstruktor számára legalább védett láthatóságot kell használnunk, ami azt jelenti, hogy ugyanabban a csomagban más osztályok is létrehozhatnak értékobjektumokat anélkül, hogy megadnák a tulajdonságaik értékeit.

3.2. Komplex típusok

Sajnos nem számíthatunk arra, hogy a JPA automatikusan társítja a harmadik féltől származó összetett típusokat táblázatokba. Csak nézze meg, mennyi változást kellett bevezetnünk az előző szakaszban!

Például a mi munkánkkal Rendelés összesítve továbbra is nehézségekkel találkozunk Joda Money mezők.

Ilyen esetben az egyedi típus megírásával járhatunk @ Converter elérhető a JPA 2.1-től. Ehhez azonban további munkára lehet szükség.

Alternatív megoldásként fel is oszthatjuk a Pénz tulajdonság két alapvető tulajdonságra. Például Húr a pénzegységre és BigDecimal a tényleges értékre.

Miközben elrejthetjük a megvalósítás részleteit, és továbbra is használhatjuk Pénz osztály a nyilvános módszerek API-ján keresztül, a gyakorlat azt mutatja, hogy a legtöbb fejlesztő nem tudja igazolni a többletmunkát, és egyszerűen degenerálná a modellt, hogy megfeleljen a JPA specifikációjának.

3.3. Következtetés

Bár a JPA a világ egyik legjobban elfogadott specifikációja, lehet, hogy nem ez a legjobb megoldás a mi kitartásunk fenntartására Rendelés összesíteni.

Ha azt akarjuk, hogy modellünk tükrözze a valódi üzleti szabályokat, akkor azt úgy kell megterveznünk, hogy az ne legyen az alapul szolgáló táblák egyszerű 1: 1 arányú ábrázolása.

Alapvetően három lehetőségünk van itt:

  1. Hozzon létre egy sor egyszerű adatosztályt, és használja őket a gazdag üzleti modell fennmaradásához és újrateremtéséhez. Sajnos ez sok plusz munkát igényelhet.
  2. Fogadja el a JPA korlátait és válassza ki a megfelelő kompromisszumot.
  3. Vegyünk egy másik technológiát.

Az első lehetőség rejlik a legnagyobb potenciálban. A gyakorlatban a legtöbb projekt a második lehetőség felhasználásával készül.

Vegyünk egy másik technológiát az aggregátumok megőrzésére.

4. Dokumentumtár

A dokumentumtár az adatok tárolásának alternatív módja. Relációk és táblázatok helyett egész objektumokat mentünk. Ezáltal a dokumentumtároló potenciálisan tökéletes jelölt lehet a tartós összesítésekre.

Ennek az oktatóanyagnak a szükségességére összpontosítunk a JSON-szerű dokumentumokon.

Vizsgáljuk meg közelebbről, hogyan néz ki megrendelés-tartóssági problémánk egy olyan dokumentumtárban, mint a MongoDB.

4.1. Állandó összesítés a MongoDB használatával

Most jó néhány adatbázis tárolja a JSON adatokat, amelyek közül az egyik népszerű a MongoDB. A MongoDB valójában bináris formában tárolja a BSON-t vagy a JSON-ot.

A MongoDB-nek köszönhetően tárolhatjuk a Rendelés példa összesítés amint az.

Mielőtt továbblépnénk, tegyük hozzá a Spring Boot MongoDB indítót:

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

Most egy hasonló tesztesetet futtathatunk, mint a JPA példájában, de ezúttal a MongoDB használatával:

@DisplayName ("adott sorrend két sorral, ha a mongo adattár használatakor továbbra is fennáll, akkor a megrendelés el lesz mentve") @Test void test () dobja a Kivételt {// adott rendelési sorrend = PreparTestOrderWithTwoLineItems (); // amikor repo.save (rendelés); // majd List foundOrders = repo.findAll (); assertThat (foundOrders) .hasSize (1); List foundOrderLines = foundOrders.iterator () .next () .getOrderLines (); assertThat (foundOrderLines) .hasSize (2); assertThat (foundOrderLines) .containOnlyElementsOf (order.getOrderLines ()); }

Ami fontos - nem cseréltük az eredetit Rendelés osztályok összesítése egyáltalán; nem szükséges alapértelmezett konstruktorokat, beállítókat vagy egyedi átalakítókat létrehozni a Pénz osztály.

És itt van a mi Rendelés aggregátum jelenik meg a boltban:

{"_id": ObjectId ("5bd8535c81c04529f54acd14"), "orderLines": [{"product": {"price": {"money": {"currency": {"code": "USD", "numericCode": 840, "decimalPlaces": 2}, "összeg": "10.00"}}}, "mennyiség": 2}, {"termék": {"ár": {"pénz": {"valuta": {"kód ":" USD "," numericCode ": 840," decimalPlaces ": 2}," összeg ":" 5.00 "}}}," mennyiség ": 10}]," totalCost ": {" pénz ": {" valuta ": {" code ":" USD "," numericCode ": 840," decimalPlaces ": 2}," összeg ":" 70.00 "}}," _class ":" com.baeldung.ddd.order.mongo.Order "}

Ez az egyszerű BSON dokumentum tartalmazza az egészet Rendelés összesítve egy darabban, szépen illeszkedve eredeti elképzelésünkhöz, miszerint mindezeknek együttesen következeteseknek kell lenniük.

Vegye figyelembe, hogy a BSON-dokumentumban található összetett objektumok egyszerűen sorosak, mint a JSON szokásos tulajdonságai. Ennek köszönhetően még harmadik féltől származó osztályok is (pl Joda Money) könnyen sorozatba sorolható, anélkül, hogy egyszerűsíteni kellene a modellt.

4.2. Következtetés

Az aggregátumok kitartása a MongoDB használatával egyszerűbb, mint a JPA használata.

Ez egyáltalán nem azt jelenti, hogy a MongoDB felülmúlja a hagyományos adatbázisokat. Rengeteg jogos eset van, amelyekben meg sem szabad próbálnunk osztályainkat aggregátumokként modellezni, és inkább SQL-adatbázist használni.

Mégis, amikor azonosítottunk egy olyan objektumcsoportot, amelynek az összetett követelményeknek megfelelően mindig konzisztensnek kell lennie, akkor a dokumentumtár használata nagyon vonzó lehet.

5. Következtetés

A DDD-ben az aggregátumok általában a rendszer legösszetettebb objektumait tartalmazzák. A velük való együttműködéshez nagyon más megközelítésre van szükség, mint a legtöbb CRUD alkalmazásban.

A népszerű ORM megoldások használata leegyszerűsített vagy túlterhelt tartománymodellhez vezethet, amely gyakran nem képes kifejezni vagy betartatni a bonyolult üzleti szabályokat.

A dokumentumtárolók megkönnyíthetik az összesítések kitartását anélkül, hogy feláldoznák a modell komplexitását.

Az összes példa teljes forráskódja elérhető a GitHub oldalon.