Brug af JaVers til datamodelrevision i forårsdata

1. Oversigt

I denne vejledning ser vi, hvordan du opsætter og bruger JaVers i en simpel Spring Boot-applikation til at spore ændringer af enheder.

2. JaVers

Når vi beskæftiger os med ændrede data, har vi normalt kun den sidste tilstand for en enhed, der er gemt i en database. Som udviklere bruger vi meget tid på at fejle en applikation og søge i logfiler efter en begivenhed, der ændrede en tilstand. Dette bliver endnu vanskeligere i produktionsmiljøet, når mange forskellige brugere bruger systemet.

Heldigvis har vi gode værktøjer som JaVers. JaVers er en rammeliste over auditlog, der hjælper med at spore ændringer af enheder i applikationen.

Brugen af ​​dette værktøj er ikke begrænset til kun fejlretning og revision. Det kan med succes anvendes til at udføre analyse, tvinge sikkerhedspolitikker og vedligeholde hændelsesloggen også.

3. Projektopsætning

Først og fremmest, for at begynde at bruge JaVers, er vi nødt til at konfigurere revisionslageret til vedvarende snapshots af enheder. For det andet skal vi justere nogle konfigurerbare egenskaber for JaVers. Endelig vil vi også dække, hvordan vi konfigurerer vores domænemodeller korrekt.

Men det er værd at nævne, at JaVers giver standardkonfigurationsindstillinger, så vi kan begynde at bruge det næsten uden konfiguration.

3.1. Afhængigheder

Først skal vi tilføje JaVers Spring Boot-startafhængighed til vores projekt. Afhængigt af typen af ​​persistenslagring har vi to muligheder: org.javers: javers-spring-boot-starter-sql og org.javers: javers-spring-boot-starter-mongo. I denne vejledning bruger vi Spring Boot SQL starter.

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

Da vi skal bruge H2-databasen, lad os også inkludere denne afhængighed:

 com.h2database h2 

3.2. JaVers Repository Setup

JaVers bruger et arkivabstraktion til lagring af forpligtelser og serialiserede enheder. Alle data er gemt i JSON-format. Derfor kan det være en god pasform at bruge et NoSQL-lager. Af hensyn til enkelheden bruger vi dog en H2-hukommelse i hukommelsen.

Som standard udnytter JaVers en implementering af lager i hukommelsen, og hvis vi bruger Spring Boot, er der ikke behov for ekstra konfiguration. Desuden, mens vi bruger Spring Data starters, genbruger JaVers databasekonfigurationen til applikationen.

JaVers leverer to startere til SQL- og Mongo-persistensstakke. De er kompatible med Spring Data og kræver ikke ekstra konfiguration som standard. Vi kan dog altid tilsidesætte standardkonfigurationsbønner: JaversSqlAutoConfiguration.java og JaversMongoAutoConfiguration.java henholdsvis.

3.3. JaVers Properties

JaVers tillader konfiguration af flere muligheder, selvom standardindstillingerne for Spring Boot er tilstrækkelige i de fleste brugssager.

Lad os kun tilsidesætte en, newObjectSnapshot, så vi kan få snapshots af nyoprettede objekter:

javers.newObjectSnapshot = sandt 

3.4. JaVers Domain Configuration

JaVers definerer internt følgende typer: Enheder, værdiobjekter, værdier, containere og primitiver. Nogle af disse udtryk kommer fra DDD (Domain Driven Design) terminologi.

Hovedformålet med at have flere typer er at tilvejebringe forskellige diffalgoritmer afhængigt af typen. Hver type har en tilsvarende diff-strategi. Som en konsekvens, hvis applikationsklasser er konfigureret forkert, får vi uforudsigelige resultater.

For at fortælle JaVers, hvilken type der skal bruges til en klasse, har vi flere muligheder:

  • Eksplicit - den første mulighed er eksplicit at bruge Tilmeld* metoder til JaversBuilder klasse - den anden måde er at bruge kommentarer
  • Implicit - JaVers leverer algoritmer til automatisk registrering af typer baseret på klasseforhold
  • Standardindstillinger - som standard vil JaVers behandle alle klasser som ValueObjects

I denne vejledning konfigurerer vi JaVers eksplicit ved hjælp af annoteringsmetoden.

Det store er, at JaVers er kompatibel med javax.persistence kommentarer. Som et resultat behøver vi ikke bruge JaVers-specifikke kommentarer på vores enheder.

4. Eksempel på projekt

Nu skal vi oprette en simpel applikation, der inkluderer flere domæneenheder, som vi skal kontrollere.

4.1. Domæne modeller

Vores domæne inkluderer butikker med produkter.

Lad os definere butik enhed:

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

Bemærk, at vi bruger standard JPA-kommentarer. JaVers kortlægger dem på følgende måde:

  • @ javax.persistence.Entity er kortlagt til @ org.javers.core.metamodel.annotation.Entity
  • @ javax.persistence. kan integreres er kortlagt til @ org.javers.core.metamodel.annotation.ValueObject.

Integrerede klasser er defineret på den sædvanlige måde:

@Embeddable public class Address {private String address; privat heltal zipCode; }

4.2. Datalager

For at revidere JPA-arkiver leverer JaVers @JaversSpringDataAuditable kommentar.

Lad os definere StoreRepository med denne kommentar:

@JaversSpringDataAuditable offentlig grænseflade StoreRepository udvider CrudRepository {}

Desuden har vi ProductRepository, men ikke kommenteret:

offentlig grænseflade ProductRepository udvider CrudRepository {}

Overvej nu et tilfælde, hvor vi ikke bruger Spring Data-arkiver. JaVers har en anden annotering på metodeniveau til dette formål: @JaversAuditable.

For eksempel kan vi definere en metode til vedvarende et produkt som følger:

@JaversAuditable public void saveProduct (Product product) {// gem objekt)

Alternativt kan vi endda tilføje denne kommentar direkte over en metode i lagergrænsefladen:

offentlig grænseflade ProductRepository udvider CrudRepository {@Override @JaversAuditable S save (S s); }

4.3. Forfatterudbyder

Hver begået ændring i JaVers bør have sin forfatter. Desuden støtter JaVers Spring Security ud af kassen.

Som et resultat foretages hver forpligtelse af en bestemt godkendt bruger. Til denne vejledning opretter vi dog en virkelig enkel brugerdefineret implementering af Forfatterudbyder Interface:

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

Og som det sidste trin, for at få JaVers til at bruge vores tilpassede implementering, er vi nødt til at tilsidesætte standardkonfigurationsbønnen:

@Bean public AuthorProvider provideJaversAuthor () {returner ny SimpleAuthorProvider (); }

5. JaVers Audit

Endelig er vi klar til at revidere vores ansøgning. Vi bruger en simpel controller til at sende ændringer i vores applikation og hente JaVers-forpligtelsesloggen. Alternativt kan vi også få adgang til H2-konsollen for at se den interne struktur i vores database:

Lad os bruge en for at få nogle indledende eksempeldata EventListener at udfylde vores database med nogle produkter:

@EventListener offentlig ugyldighed appReady (ApplicationReadyEvent begivenhed) {Store store = ny butik ("Baeldung butik", ny adresse ("Nogle gader", 22222)); for (int i = 1; i <3; i ++) {Produkt produkt = nyt produkt ("Produkt #" + i, 100 * i); store.addProduct (produkt); } storeRepository.save (butik); }

5.1. Indledende forpligtelse

Når et objekt oprettes, JaVers først forpligter sig til INITIAL type.

Lad os kontrollere snapshots efter applikationens opstart:

@GetMapping ("/ butikker / snapshots") offentlige String getStoresSnapshots () {QueryBuilder jqlQuery = QueryBuilder.byClass (Store.class); Liste snapshots = javers.findSnapshots (jqlQuery.build ()); returnere javers.getJsonConverter (). toJson (snapshots); }

I koden ovenfor spørger vi JaVers om snapshots til butik klasse. Hvis vi anmoder om dette slutpunkt, 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}, {" enhed ":" com.baeldung.springjavers.domain.Product "," cdoId ": 3}]}," changedProperties ": [" address "," name "," id "," products "]," type ": "INITIAL", "version": 1}]

Bemærk, at snapshotet ovenfor inkluderer alle produkter tilføjet til butikken på trods af den manglende kommentar til ProductRepository interface.

Som standard vil JaVers revidere alle relaterede modeller af en samlet rod, hvis de vedholdes sammen med den overordnede.

Vi kan fortælle JaVers at ignorere bestemte klasser ved hjælp af DiffIgnore kommentar.

For eksempel kan vi kommentere Produkter felt med kommentaren i butik enhed:

@DiffIgnore private List produkter = ny ArrayList ();

Derfor sporer JaVers ikke ændringer af produkter, der stammer fra butik enhed.

5.2. Opdater Forpligtelse

Den næste type forpligtelse er OPDATER begå. Dette er den mest værdifulde forpligtelsestype, da den repræsenterer ændringer i et objekts tilstand.

Lad os definere en metode, der opdaterer butiksenheden og alle produkter i butikken:

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

Hvis vi kører denne metode, får vi følgende linje i debug-output (i tilfælde af de samme produkter og butikker tæller):

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

Da JaVers har vedvarende ændringer med succes, lad os forespørge på snapshots for produkter:

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

Vi får tidligere INITIAL forpligter sig og nyt OPDATER forpligter sig til:

 {"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 alle oplysninger om den ændring, vi foretog.

Det er værd at bemærke det JaVers opretter ikke nye forbindelser til databasen. I stedet genbruger det eksisterende forbindelser. JaVers-data begås eller rulles tilbage sammen med applikationsdata i den samme transaktion.

5.3. Ændringer

JaVers registrerer ændringer som atomforskelle mellem versioner af et objekt. Som vi kan se fra JaVers-ordningen, er der ingen separat tabel til lagring af ændringer, så JaVers beregner ændringer dynamisk som forskellen mellem snapshots.

Lad os opdatere en produktpris:

offentlig ugyldig opdateringProductPrice (Integer productId, Double price) {Valgfri productOpt = productRepository.findById (productId); productOpt.ifPresent (produkt -> {product.setPrice (pris); productRepository.save (produkt);}); }

Lad os så spørge JaVers om ændringer:

@GetMapping ("/ products / {productId} / ændringer") offentlig String getProductChanges (@PathVariable int productId) {Product product = storeService.findProductById (productId); QueryBuilder jqlQuery = QueryBuilder.byInstance (produkt); Ændringer ændringer = javers.findChanges (jqlQuery.build ()); returnere javers.getJsonConverter (). toJson (ændringer); }

Outputtet indeholder den ændrede egenskab og dens værdier før og efter:

[{"changeType": "ValueChange", "globalId": {"entity": "com.baeldung.springjavers.domain.Product", "cdoId": 2}, "commitMetadata": {"author": "Baeldung Author "," egenskaber ": []," 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 at opdage en type ændring sammenligner JaVers efterfølgende snapshots af et objekts opdateringer. I ovenstående tilfælde har vi ændret ejendommen til den enhed, vi har PROPERTY_VALUE_CHANGED skift type.

5.4. Skygger

Desuden giver JaVers en anden visning af opkaldte reviderede enheder Skygge. En skygge repræsenterer en objekttilstand, der er gendannet fra snapshots. Dette koncept er tæt knyttet til begivenhedssourcing.

Der er fire forskellige anvendelsesområder for Shadows:

  • Overfladisk - skygger oprettes ud fra et øjebliksbillede valgt i en JQL-forespørgsel
  • Child-værdi-objekt - skygger indeholder alle underordnede værdiobjekter, der ejes af udvalgte enheder
  • Forpligt dig dybt - skygger oprettes fra alle snapshots relateret til valgte enheder
  • Dyb + - JaVers forsøger at gendanne fulde objektgrafer med (muligvis) alle genstande indlæst.

Lad os bruge omfanget af Child-value-object og få en skygge for en enkelt butik:

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

Som et resultat får vi butiksenheden med Adresse værdi 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 at få produkter i resultatet kan vi anvende det omfattende forpligtelsesområde.

6. Konklusion

I denne tutorial har vi set, hvor let JaVers integreres med især Spring Boot og Spring Data. Alt i alt kræver JaVers næsten nul konfiguration for at konfigurere.

For at konkludere kan JaVers have forskellige applikationer, fra fejlfinding til kompleks analyse.

Det fulde projekt til denne artikel er tilgængelig på GitHub.


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