Korištenje JaVers-a za reviziju modela podataka u proljetnim podacima

1. Pregled

U ovom uputstvu vidjet ćemo kako postaviti i koristiti JaVers u jednostavnoj aplikaciji Spring Boot za praćenje promjena entiteta.

2. JaVers

Kad imamo posla s promjenjivim podacima, obično imamo samo posljednje stanje entiteta pohranjeno u bazi podataka. Kao programeri, trošimo puno vremena na uklanjanje pogrešaka u aplikaciji, pretražujući datoteke dnevnika za događaj koji je promijenio stanje. To postaje još zamršenije u proizvodnom okruženju kada sustav koristi mnogo različitih korisnika.

Srećom, imamo izvrsne alate poput JaVersa. JaVers je okvir dnevnika revizije koji pomaže u praćenju promjena entiteta u aplikaciji.

Korištenje ovog alata nije ograničeno samo na otklanjanje pogrešaka i reviziju. Može se uspješno primijeniti za obavljanje analiza, prisiljavanje sigurnosnih politika i održavanje dnevnika događaja.

3. Postavljanje projekta

Prije svega, da bismo počeli koristiti JaVers, moramo konfigurirati spremište revizije za trajne snimke entiteta. Drugo, moramo prilagoditi neka podesiva svojstva JaVers-a. Na kraju ćemo također pokriti kako pravilno konfigurirati naše modele domene.

No, vrijedi spomenuti da JaVers nudi zadane mogućnosti konfiguracije, tako da ga možemo početi koristiti gotovo bez konfiguracije.

3.1. Ovisnosti

Prvo, u naš projekt moramo dodati ovisnost o pokretaču JaVers Spring Boot. Ovisno o vrsti trajnog skladištenja, imamo dvije mogućnosti: org.javers: javers-spring-boot-starter-sql i org.javers: javers-spring-boot-starter-mongo. U ovom uputstvu koristit ćemo Spring Boot SQL starter.

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

Kako ćemo koristiti H2 bazu podataka, uključimo i ovu ovisnost:

 com.h2data baza podataka h2 

3.2. Postavljanje spremišta JaVers

JaVers koristi apstrakciju spremišta za spremanje predavanja i serializiranih entiteta. Svi podaci pohranjeni su u JSON formatu. Stoga bi moglo biti dobro koristiti NoSQL pohranu. Međutim, radi jednostavnosti, koristit ćemo instancu H2 u memoriji.

Prema zadanim postavkama, JaVers koristi implementaciju spremišta u memoriji, a ako koristimo Spring Boot, nema potrebe za dodatnom konfiguracijom. Nadalje, dok koristi pokretače Spring Data, JaVers ponovno koristi konfiguraciju baze podataka za aplikaciju.

JaVers nudi dva početna elementa za SQL i Mongo postojanosti. Kompatibilni su s Spring Data i prema zadanim postavkama ne zahtijevaju dodatnu konfiguraciju. Međutim, uvijek možemo poništiti zadane građe za konfiguraciju: JaversSqlAutoConfiguration.java i JaversMongoAutoConfiguration.java odnosno.

3.3. JaVers svojstva

JaVers omogućuje konfiguriranje nekoliko opcija, iako su zadane postavke Spring Boot u većini slučajeva dovoljne.

Zamijenimo samo jedan, newObjectSnapshot, kako bismo mogli dobiti snimke novostvorenih objekata:

javers.newObjectSnapshot = true 

3.4. JaVers konfiguracija domene

JaVers interno definira sljedeće vrste: Entiteti, Vrijednosni objekti, Vrijednosti, Spremnici i Primitivi. Neki od ovih izraza potječu iz DDD (Domain Driven Design) terminologije.

Glavna svrha posjedovanja nekoliko tipova je pružanje različitih algoritama za razlikovanje ovisno o vrsti. Svaka vrsta ima odgovarajuću strategiju razlikovanja. Kao posljedica toga, ako su klase aplikacija konfigurirane pogrešno, dobit ćemo nepredvidive rezultate.

Da bismo JaVersima rekli koji tip koristiti za nastavu, imamo nekoliko mogućnosti:

  • Eksplicitno - prva opcija je izričita upotreba Registar* metode JaversBuilder razred - drugi način je korištenje bilješki
  • Implicitno - JaVers nudi algoritme za automatsko otkrivanje tipova na temelju odnosa klase
  • Zadane postavke - prema zadanim postavkama, JaVers će se prema svim razredima ponašati kao prema ValueObjects

U ovom ćemo uputstvu JaVers izričito konfigurirati, koristeći metodu bilježenja.

Sjajna stvar je u tome JaVers je kompatibilan s javax.postojanost bilješke. Kao rezultat toga, nećemo trebati koristiti napomene specifične za JaVers na našim entitetima.

4. Uzorak projekta

Sada ćemo stvoriti jednostavnu aplikaciju koja će uključivati ​​nekoliko entiteta domene koje ćemo revidirati.

4.1. Modeli domena

Naša domena će obuhvaćati trgovine s proizvodima.

Definirajmo Spremi entitet:

@Entity javna trgovina {@Id @GeneratedValue private int id; privatni naziv niza; Adresa uložene privatne adrese; @OneToMany (mappedBy = "store", cascade = CascadeType.ALL, orphanRemoval = true) private List products = new ArrayList (); // konstruktori, getteri, postavljači}

Imajte na umu da koristimo zadane JPA bilješke. JaVers ih mapira na sljedeći način:

  • @ javax.persistence.Entity mapira se na @ org.javers.core.metamodel.annotation.Entity
  • @ javax.persistence.Embedvable mapira se na @ org.javers.core.metamodel.annotation.ValueObject.

Klase za ugradnju definirane su na uobičajeni način:

@Embedvable javna klasa Adresa {private String address; privatni cijeli broj zipCode; }

4.2. Spremišta podataka

Kako bi mogao revidirati JPA spremišta, JaVers nudi @JaversSpringDataAuditable bilješka.

Definirajmo StoreRepository uz napomenu:

@JaversSpringDataAuditable javno sučelje StoreRepository proširuje CrudRepository {}

Nadalje, imat ćemo Spremište proizvoda, ali bez bilješki:

javno sučelje ProductRepository proširuje CrudRepository {}

Sada razmotrimo slučaj kada ne koristimo spremišta Spring Data. JaVers u tu svrhu ima još jednu napomenu na razini metode: @JaversAuditable.

Na primjer, metodu trajnosti proizvoda možemo definirati na sljedeći način:

@JaversAuditable public void saveProduct (Product product) {// save object}

Alternativno, ovu bilješku možemo dodati izravno iznad metode u sučelju spremišta:

javno sučelje ProductRepository proširuje CrudRepository {@Override @JaversAuditable S save (S); }

4.3. Pružatelj usluga

Svaka izvršena promjena u JaVersu trebala bi imati svog autora. Štoviše, JaVers od početka podržava Spring Security.

Kao rezultat toga, svako urezivanje vrši određeni ovjereni korisnik. Međutim, za ovaj tutorial stvorit ćemo zaista jednostavnu prilagođenu implementaciju AutorProvider Sučelje:

privatna statička klasa SimpleAuthorProvider implementira AuthorProvider {@Override public String provide () {return "Baeldung Author"; }}

I kao posljednji korak, da bi JaVeri koristili našu prilagođenu implementaciju, moramo nadjačati zadani konfiguracijski grah:

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

5. JaVers revizija

Napokon, spremni smo za reviziju naše prijave. Upotrijebit ćemo jednostavan kontroler za slanje promjena u našu aplikaciju i dohvaćanje dnevnika JaVers predavanja. Alternativno, također možemo pristupiti H2 konzoli kako bismo vidjeli unutarnju strukturu naše baze podataka:

Da bismo imali neke početne uzorke podataka, upotrijebimo EventListener popuniti našu bazu podataka nekim proizvodima:

@EventListener public void appReady (ApplicationReadyEvent event) {Store store = new Store ("Baeldung store", nova adresa ("Some street", 22222)); for (int i = 1; i <3; i ++) {Product product = novi proizvod ("Product #" + i, 100 * i); store.addProduct (proizvod); } storeRepository.save (store); }

5.1. Početno predavanje

Kada se kreira objekt, JaVers prvi se obvezuje na POČETNA tip.

Provjerimo snimke nakon pokretanja aplikacije:

@GetMapping ("/ store / snapshots") javni niz getStoresSnapshots () {QueryBuilder jqlQuery = QueryBuilder.byClass (Store.class); Popis snimaka = javers.findSnapshots (jqlQuery.build ()); vratiti javers.getJsonConverter (). toJson (snimke); }

U gornjem kodu tražimo od JaVersa snimke za Spremi razred. Ako uputimo zahtjev ovoj krajnjoj točki, dobit ćemo rezultat poput dolje navedenog:

[{"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": " adresa "}," name ":" Baeldung store "," id ": 1," products ": [{" entity ":" com.baeldung.springjavers.domain.Product "," cdoId ": 2}, {" entitet ":" com.baeldung.springjavers.domain.Product "," cdoId ": 3}]}," changedProperties ": [" adresa "," ime "," id "," proizvodi "]," vrsta ": "INICIAL", "version": 1}]

Imajte na umu da je gornja snimka uključuje sve proizvode dodane u trgovinu, unatoč nedostatku napomene za Spremište proizvoda sučelje.

Prema zadanim postavkama, JaVers će revidirati sve povezane modele agregatnog korijena ako se nastave zajedno s roditeljem.

JaVerima možemo reći da ignoriraju određene klase pomoću DiffIgnore bilješka.

Na primjer, možemo označiti proizvoda polje s bilješkom u Spremi entitet:

@DiffIgnore privatni popis proizvoda = novi ArrayList ();

Slijedom toga, JaVers neće pratiti promjene proizvoda porijeklom iz Spremi entitet.

5.2. Ažuriraj Porezi

Sljedeća vrsta predavanja je AŽURIRANJE počiniti. Ovo je najvrjednija vrsta urezivanja jer predstavlja promjene stanja objekta.

Definirajmo metodu koja će ažurirati entitet trgovine i sve proizvode u trgovini:

javna void rebrandStore (int storeId, String updatedName) {Neobvezno storeOpt = storeRepository.findById (storeId); storeOpt.ifPresent (store -> {store.setName (updatedName); store.getProducts (). forEach (product -> {product.setNamePrefix (updatedName);}); storeRepository.save (store);}); }

Ako pokrenemo ovu metodu, dobit ćemo sljedeći redak u izlazu za otklanjanje pogrešaka (u slučaju istih proizvoda i trgovina):

11: 29: 35.439 [http-nio-8080-exec-2] INFO org.javers.core.Javers - Povlačenje (id: 2.0, snimke: 3, autor: Baeldung Autor, promjene - ValueChange: 3), izvedeno u 48 milis (razlika: 43, upornost: 5)

Budući da je JaVers uspješno ustrajao na promjenama, upitajmo snimke proizvoda:

@GetMapping ("/ products / snapshots") javni niz getProductSnapshots () {QueryBuilder jqlQuery = QueryBuilder.byClass (Product.class); Popis snimaka = javers.findSnapshots (jqlQuery.build ()); vratiti javers.getJsonConverter (). toJson (snimke); }

Dobit ćemo prethodno POČETNA obvezuje i novo AŽURIRANJE obvezuje:

 {"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}}}

Ovdje možemo vidjeti sve informacije o promjeni koju smo izvršili.

Vrijedno je to napomenuti JaVers ne stvara nove veze s bazom podataka. Umjesto toga, ponovno koristi postojeće veze. JaVers podaci predaju se ili vraćaju zajedno s podacima aplikacije u istoj transakciji.

5.3. Promjene

JaVers bilježi promjene kao atomske razlike između verzija objekta. Kao što možemo vidjeti iz JaVersove sheme, ne postoji posebna tablica za pohranu promjena, pa JaVers dinamički izračunava promjene kao razliku između snimaka.

Ažurirajmo cijenu proizvoda:

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

Zatim, upitajmo JaVers za promjene:

@GetMapping ("/ products / {productId} / changes") javni niz getProductChanges (@PathVariable int productId) {Product product = storeService.findProductById (productId); QueryBuilder jqlQuery = QueryBuilder.byInstance (proizvod); Promjene se mijenjaju = javers.findChanges (jqlQuery.build ()); vratiti javers.getJsonConverter (). toJson (promjene); }

Izlaz sadrži promijenjeno svojstvo i njegove vrijednosti prije i poslije:

[{"changeType": "ValueChange", "globalId": {"entity": "com.baeldung.springjavers.domain.Product", "cdoId": 2}, "commitMetadata": {"author": "Baeldung Author "," properties ": []," commitDate ":" 2019-08-26T16: 22: 33.339 "," commitDateInstant ":" 2019-08-26T13: 22: 33.339Z "," id ": 2.00}," svojstvo ":" cijena "," propertyChangeType ":" PROPERTY_VALUE_CHANGED "," lijevo ": 100,0," desno ": 3333,0}]

Da bi otkrio vrstu promjene, JaVers uspoređuje sljedeće snimke ažuriranja objekta. U gornjem slučaju, jer smo promijenili svojstvo entiteta, imamo PROPERTY_VALUE_CHANGED promijeniti tip.

5.4. Sjene

Štoviše, JaVers pruža još jedan pogled na subjekte revizije pod nazivom Sjena. Sjena predstavlja stanje objekta vraćeno iz snimaka. Ovaj je koncept usko povezan s izvorom događaja.

Postoje četiri različita područja primjene za Sjene:

  • Plitko - sjene se stvaraju iz snimke odabrane u okviru JQL upita
  • Dijete-vrijednost-objekt - sjene sadrže sve podređene objekte vrijednosti u vlasništvu odabranih entiteta
  • Obvezujte se duboko - sjene se stvaraju iz svih snimaka povezanih s odabranim entitetima
  • Duboko + - JaVers pokušava vratiti cjelovite grafove objekata s (možda) svim učitanim objektima.

Iskoristimo opseg objekta Child-value-object i dobijmo sjenu za jednu trgovinu:

@GetMapping ("/ trgovine / {storeId} / sjene") javni niz getStoreShadows (@PathVariable int storeId) {Store store = storeService.findStoreById (storeId); JqlQuery jqlQuery = QueryBuilder.byInstance (store) .withChildValueObjects (). Build (); Popis sjene = javers.findShadows (jqlQuery); vratiti javers.getJsonConverter (). toJson (shadows.get (0)); }

Kao rezultat, dobit ćemo entitet trgovine s Adresa objekt vrijednosti:

{"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}, "proizvodi": []}}

Da bismo dobili rezultate u rezultatima, možemo primijeniti opseg dubokog predavanja.

6. Zaključak

U ovom uputstvu vidjeli smo kako se lako JaVers integrira s Spring Boot i Spring Data, posebno. Sve u svemu, JaVers zahtijeva gotovo nultu konfiguraciju za postavljanje.

Da zaključimo, JaVers mogu imati različite primjene, od otklanjanja pogrešaka do složene analize.

Cijeli projekt za ovaj članak dostupan je na GitHubu.