Bruke JaVers til datamodellrevisjon i vårdata

1. Oversikt

I denne opplæringen vil vi se hvordan du konfigurerer og bruker JaVers i et enkelt Spring Boot-program for å spore endringer av enheter.

2. JaVers

Når vi arbeider med foranderlige data, har vi vanligvis bare den siste tilstanden til en enhet som er lagret i en database. Som utviklere bruker vi mye tid på feilsøking av et program, og søker i loggfiler for en hendelse som endret en tilstand. Dette blir enda vanskeligere i produksjonsmiljøet når mange forskjellige brukere bruker systemet.

Heldigvis har vi gode verktøy som JaVers. JaVers er et rammeverk for revisjonslogg som hjelper med å spore endringer av enheter i applikasjonen.

Bruken av dette verktøyet er ikke begrenset til bare feilsøking og revisjon. Det kan brukes med hell for å utføre analyser, tvinge sikkerhetspolitikk og vedlikeholde hendelsesloggen også.

3. Prosjektoppsett

Først og fremst, for å begynne å bruke JaVers, må vi konfigurere revisjonsdatabasen for vedvarende øyeblikksbilder av enheter. For det andre må vi justere noen konfigurerbare egenskaper til JaVers. Til slutt vil vi også dekke hvordan vi konfigurerer domenemodellene våre riktig.

Men det er verdt å nevne at JaVers tilbyr standard konfigurasjonsalternativer, slik at vi kan begynne å bruke den nesten uten konfigurasjon.

3.1. Avhengigheter

Først må vi legge til JaVers Spring Boot startavhengighet til prosjektet vårt. Avhengig av type utholdenhetslagring, har vi to alternativer: org.javers: javers-spring-boot-starter-sql og org.javers: javers-spring-boot-starter-mongo. I denne opplæringen bruker vi vårstart Boot SQL starter.

 org.javers javers-spring-boot-starter-sql 5.6.3 

Når vi skal bruke H2-databasen, la oss også inkludere denne avhengigheten:

 com.h2database h2 

3.2. JaVers Repository Setup

JaVers bruker en reproduksjonsabstraksjon for lagring av forpliktelser og serielle enheter. Alle data lagres i JSON-format. Derfor kan det være bra å bruke NoSQL-lagring. For enkelhets skyld bruker vi imidlertid en H2-minne i minnet.

Som standard utnytter JaVers en implementering av lager i minnet, og hvis vi bruker Spring Boot, er det ikke behov for ekstra konfigurasjon. Dessuten, mens du bruker Spring Data starters, gjenbruker JaVers databasekonfigurasjonen for applikasjonen.

JaVers tilbyr to forretter for utholdenhetsstabler for SQL og Mongo. De er kompatible med vårdata og krever ikke ekstra konfigurasjon som standard. Vi kan imidlertid alltid overstyre standardkonfigurasjonsbønner: JaversSqlAutoConfiguration.java og JaversMongoAutoConfiguration.java henholdsvis.

3.3. JaVers Properties

JaVers tillater konfigurering av flere alternativer, selv om standardinnstillingene for Spring Boot er tilstrekkelig i de fleste bruksområder.

La oss overstyre bare en, newObjectSnapshot, slik at vi kan få øyeblikksbilder av nyopprettede objekter:

javers.newObjectSnapshot = sant 

3.4. JaVers Domain Configuration

JaVers definerer internt følgende typer: Enheter, Verdiobjekter, Verdier, Beholdere og Primitiver. Noen av disse begrepene kommer fra DDD (Domain Driven Design) terminologi.

Hovedformålet med å ha flere typer er å gi forskjellige diffalgoritmer avhengig av type. Hver type har en tilsvarende diff-strategi. Som en konsekvens, hvis applikasjonsklasser er konfigurert feil, får vi uforutsigbare resultater.

For å fortelle JaVers hvilken type som skal brukes til en klasse, har vi flere alternativer:

  • Eksplisitt - det første alternativet er å eksplisitt bruke registrere* metoder for JaversBuilder klasse - den andre måten er å bruke merknader
  • Implisitt - JaVers gir algoritmer for å oppdage typer automatisk basert på klasseforhold
  • Standardinnstillinger - som standard vil JaVers behandle alle klassene som ValueObjects

I denne veiledningen konfigurerer vi JaVers eksplisitt ved å bruke merknadsmetoden.

Det flotte er at JaVers er kompatibel med javax. utholdenhet kommentarer. Som et resultat trenger vi ikke bruke JaVers-spesifikke merknader på enhetene våre.

4. Eksempel på prosjekt

Nå skal vi lage en enkel applikasjon som vil omfatte flere domenenheter som vi skal revidere.

4.1. Domenemodeller

Domenet vårt vil omfatte butikker med produkter.

La oss definere butikk enhet:

@Entity public class Store {@Id @GeneratedValue private int id; privat strengnavn; @Embedded privat adresse adresse; @OneToMany (mappedBy = "store", cascade = CascadeType.ALL, orphanRemoval = true) private Liste produkter = ny ArrayList (); // konstruktører, getters, setters}

Vær oppmerksom på at vi bruker standard JPA-merknader. JaVers kartlegger dem på følgende måte:

  • @ javax.persistence.Entity er kartlagt til @ org.javers.core.metamodel.annotation.Entity
  • @ javax.persistence.Integrerbar er kartlagt til @ org.javers.core.metamodel.annotation.ValueObject.

Innbyggbare klasser er definert på vanlig måte:

@ Embeddable public class Address {privat strengadresse; privat Heltall zipCode; }

4.2. Datalager

For å revidere JPA-repositorier, tilbyr JaVers @JaversSpringDataAuditable kommentar.

La oss definere StoreRepository med den kommentaren:

@JaversSpringDataAuditable offentlig grensesnitt StoreRepository utvider CrudRepository {}

Videre vil vi ha ProductRepository, men ikke kommentert:

offentlig grensesnitt ProductRepository utvider CrudRepository {}

Vurder nå et tilfelle når vi ikke bruker Spring Data-arkiver. JaVers har en annen merknadsnivåkommentar for det formålet: @JaversAuditable.

For eksempel kan vi definere en metode for å vedvare et produkt på følgende måte:

@JaversAuditable public void saveProduct (Produktprodukt) {// lagre objekt}

Alternativt kan vi til og med legge til denne kommentaren rett over en metode i depotgrensesnittet:

offentlig grensesnitt ProductRepository utvider CrudRepository {@Override @JaversAuditable S save (S s); }

4.3. Forfatterleverandør

Hver forpliktet endring i JaVers bør ha sin forfatter. Videre støtter JaVers Spring Security ut av esken.

Som et resultat blir hver forpliktelse utført av en spesifikk godkjent bruker. For denne opplæringen lager vi imidlertid en veldig enkel tilpasset implementering av Forfatterleverandør Grensesnitt:

privat statisk klasse SimpleAuthorProvider implementerer AuthorProvider {@ Override public String supply () {return "Baeldung Author"; }}

Og som det siste trinnet, for å få JaVers til å bruke vår tilpassede implementering, må vi overstyre standardkonfigurasjonsbønnen:

@Bean public AuthorProvider provideJaversAuthor () {return new SimpleAuthorProvider (); }

5. JaVers Audit

Endelig er vi klare til å revidere søknaden vår. Vi bruker en enkel kontroller for å sende endringer i applikasjonen vår og hente JaVers-forpliktelsesloggen. Alternativt kan vi også få tilgang til H2-konsollen for å se den interne strukturen i databasen vår:

For å ha noen innledende eksempeldata, la oss bruke en EventListener for å fylle ut databasen vår med noen produkter:

@EventListener public void appReady (ApplicationReadyEvent event) {Store store = new Store ("Baeldung store", new Address ("Some street", 22222)); for (int i = 1; i <3; i ++) {Produktprodukt = nytt produkt ("Produkt #" + i, 100 * i); store.addProduct (produkt); } storeRepository.save (butikk); }

5.1. Første engasjement

Når et objekt opprettes, JaVers først forplikter seg til FØRSTE type.

La oss sjekke øyeblikksbildene etter programoppstart:

@GetMapping ("/ stores / snapshots") offentlig String getStoresSnapshots () {QueryBuilder jqlQuery = QueryBuilder.byClass (Store.class); Liste øyeblikksbilder = javers.findSnapshots (jqlQuery.build ()); return javers.getJsonConverter (). toJson (snapshots); }

I koden ovenfor spør vi JaVers om øyeblikksbilder for butikk klasse. Hvis vi ber om dette endepunktet, får vi et resultat som det nedenfor:

[{"commitMetadata": {"author": "Baeldung Author", "properties": [], "commitDate": "2019-08-26T07: 04: 06.776", "commitDateInstant": "2019-08-26T04: 04: 06.776Z "," id ": 1.00}," globalId ": {" entity ":" com.baeldung.springjavers.domain.Store "," cdoId ": 1}," state ": {" address ": {"valueObject": "com.baeldung.springjavers.domain.Address", "ownerId": {"entity": "com.baeldung.springjavers.domain.Store", "cdoId": 1}, "fragment": " adresse "}," name ":" Baeldung store "," id ": 1," products ": [{" entity ":" com.baeldung.springjavers.domain.Product "," cdoId ": 2}, {" enhet ":" com.baeldung.springjavers.domain.Product "," cdoId ": 3}]}," changedProperties ": [" address "," name "," id "," products "]," type ": "INITIAL", "version": 1}]

Merk at øyeblikksbildet ovenfor inkluderer alle produkter lagt til butikken til tross for manglende kommentar til ProductRepository grensesnitt.

Som standard vil JaVers revidere alle relaterte modeller av en samlet rot hvis de er vedvarende sammen med foreldrene.

Vi kan be JaVers om å ignorere bestemte klasser ved å bruke DiffIgnore kommentar.

For eksempel kan vi kommentere Produkter feltet med merknaden i butikk enhet:

@DiffIgnore private List produkter = ny ArrayList ();

Følgelig vil JaVers ikke spore endringer av produkter som stammer fra butikk enhet.

5.2. Oppdater Commit

Den neste typen forpliktelse er OPPDATER begå. Dette er den mest verdifulle forpliktelsestypen da den representerer endringer i et objekts tilstand.

La oss definere en metode som vil oppdatere butikkenheten og alle produktene i butikken:

public void rebrandStore (int storeId, String updatedName) {Valgfri storeOpt = storeRepository.findById (storeId); storeOpt.ifPresent (store -> {store.setName (oppdatert navn); store.getProducts (). forEach (produkt -> {product.setNamePrefix (oppdatert navn);}); storeRepository.save (butikk);}); }

Hvis vi kjører denne metoden, får vi følgende linje i feilsøkingsutgangen (i tilfelle de samme produktene og butikkene teller):

11: 29: 35.439 [http-nio-8080-exec-2] INFO org.javers.core.Javers - Commit (id: 2.0, snapshots: 3, forfatter: Baeldung Author, endringer - ValueChange: 3), gjort i 48 millis (diff: 43, persist: 5)

Siden JaVers har vedvaret endringene, kan vi spørre øyeblikksbildene for produkter:

@GetMapping ("/ products / snapshots") offentlig String getProductSnapshots () {QueryBuilder jqlQuery = QueryBuilder.byClass (Product.class); Liste øyeblikksbilder = javers.findSnapshots (jqlQuery.build ()); return javers.getJsonConverter (). toJson (snapshots); }

Vi får forrige FØRSTE forplikter og nytt OPPDATER forplikter:

 {"commitMetadata": {"author": "Baeldung Author", "properties": [], "commitDate": "2019-08-26T12: 55: 20.197", "commitDateInstant": "2019-08-26T09: 55 : 20.197Z "," id ": 2.00}," globalId ": {" entity ":" com.baeldung.springjavers.domain.Product "," cdoId ": 3}," state ": {" price ": 200.0 , "name": "NewProduct # 2", "id": 3, "store": {"entity": "com.baeldung.springjavers.domain.Store", "cdoId": 1}}}

Her kan vi se all informasjonen om endringen vi gjorde.

Det er verdt å merke seg det JaVers oppretter ikke nye tilkoblinger til databasen. I stedet gjenbruker den eksisterende forbindelser. JaVers-data blir begått eller rullet tilbake sammen med applikasjonsdata i samme transaksjon.

5.3. Endringer

JaVers registrerer endringer som atomforskjeller mellom versjoner av et objekt. Som vi kan se fra JaVers-ordningen, er det ingen egen tabell for lagring av endringer JaVers beregner endringer dynamisk som forskjellen mellom øyeblikksbilder.

La oss oppdatere en produktpris:

public void updateProductPrice (Integer productId, Double price) {Valgfritt productOpt = productRepository.findById (productId); productOpt.ifPresent (produkt -> {product.setPrice (pris); productRepository.save (produkt);}); }

La oss så spørre JaVers om endringer:

@GetMapping ("/ products / {productId} / endringer") offentlig streng getProductChanges (@PathVariable int productId) {Produktprodukt = storeService.findProductById (productId); QueryBuilder jqlQuery = QueryBuilder.byInstance (produkt); Endringer endringer = javers.findChanges (jqlQuery.build ()); return javers.getJsonConverter (). toJson (endringer); }

Utgangen inneholder den endrede egenskapen og dens verdier før og etter:

[{"changeType": "ValueChange", "globalId": {"entity": "com.baeldung.springjavers.domain.Product", "cdoId": 2}, "commitMetadata": {"author": "Baeldung Author "," egenskaper ": []," commitDate ":" 2019-08-26T16: 22: 33.339 "," commitDateInstant ":" 2019-08-26T13: 22: 33.339Z "," id ": 2.00}," property ":" price "," propertyChangeType ":" PROPERTY_VALUE_CHANGED "," left ": 100.0," right ": 3333.0}]

For å oppdage en type endring sammenligner JaVers påfølgende øyeblikksbilder av oppdateringene til et objekt. I tilfelle ovenfor har vi endret eiendommen til enheten vi har PROPERTY_VALUE_CHANGED endre type.

5.4. Skygger

Videre gir JaVers et annet syn på reviderte enheter som kalles skygge. En skygge representerer en objekttilstand gjenopprettet fra øyeblikksbilder. Dette konseptet er nært knyttet til hendelsessourcing.

Det er fire forskjellige omfang for Shadows:

  • Grunt - skygger opprettes fra et øyeblikksbilde valgt i et JQL-spørsmål
  • Barn-verdi-objekt - skygger inneholder alle underordnede verdiobjekter som eies av utvalgte enheter
  • Forplikt deg dypt - skygger opprettes fra alle øyeblikksbilder relatert til utvalgte enheter
  • Dyp + - JaVers prøver å gjenopprette fullstendige objektgrafer med (muligens) alle objekter lastet.

La oss bruke omfanget av Child-value-object og få en skygge for en enkelt butikk:

@GetMapping ("/ stores / {storeId} / shadows") offentlig streng getStoreShadows (@PathVariable int storeId) {Store store = storeService.findStoreById (storeId); JqlQuery jqlQuery = QueryBuilder.byInstance (store) .withChildValueObjects (). Build (); Liste skygger = javers.findShadows (jqlQuery); return javers.getJsonConverter (). toJson (shadows.get (0)); }

Som et resultat får vi butikkenheten med Adresse verdi objekt:

{"commitMetadata": {"author": "Baeldung Author", "properties": [], "commitDate": "2019-08-26T16: 09: 20.674", "commitDateInstant": "2019-08-26T13: 09 : 20.674Z "," id ": 1.00}," it ": {" id ": 1," name ":" Baeldung store "," address ": {" address ":" Some street "," zipCode ": 22222}, "produkter": []}}

For å få produkter i resultatet kan vi anvende forpliktelsesdyp omfanget.

6. Konklusjon

I denne opplæringen har vi sett hvor enkelt JaVers integreres med Spring Boot og Spring Data spesielt. Alt i alt krever JaVers nesten null konfigurasjon for å sette opp.

For å konkludere kan JaVers ha forskjellige applikasjoner, fra feilsøking til kompleks analyse.

Hele prosjektet for denne artikkelen er tilgjengelig på GitHub.


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