2

I am evaluating javers to use it for auditing entities. I have an Entity with nested collection of ValueObjects.I expect each attribute change on the valueobject to generate a snapshot of the Entity.Snapshot is created only when a valueobject is added to the collection.In my case i added two valueobjects to the collection which created two snapshots of the entity. On third occasion i just changed an attribute on value object, and javers didn't recognize that as a change on the entity but created a snapshot for inner value objects.

My question is whether my assumption is valid or what is the best way to track the changes to the value objects in a collection

Below is the code from a simple test i have created using spring boot.

I am using javers version 3.2.0

My entity is as below

package com.example.javersdemo;

import com.fasterxml.jackson.annotation.JsonUnwrapped;
import lombok.Data;
import org.javers.core.metamodel.annotation.TypeName;

import javax.persistence.CollectionTable;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import java.util.List;

@Data
@Entity
@TypeName("User")
class User{
    @Id
    private String name ;


    @JsonUnwrapped
    @ElementCollection(targetClass = Hobby.class)
    @CollectionTable(name = "USER_HOBBIES")
    @JoinColumn(name = "NAME")
    private List<Hobby> hobbies;

    private User(){

    }

    public User(String name, List<Hobby> hobbies) {
        this.name = name;
        this.hobbies = hobbies;
    }
}

Value object is as below

package com.example.javersdemo;

import lombok.Data;

import javax.persistence.Embeddable;

@Data
@Embeddable
public class Hobby {

    private String hobby;

    private boolean active;

    private Hobby() {

    }

    public Hobby(String hobby, boolean active) {

        this.hobby = hobby;
        this.active = active;
    }
}

My spring data repository is as below

package com.example.javersdemo;

import org.javers.spring.annotation.JaversSpringDataAuditable;
import org.springframework.data.repository.CrudRepository;


@JaversSpringDataAuditable
interface TestUserRepository extends CrudRepository<User,String> {

}

Below is a spock integration test i have created to verify the changes to the object attributes inside a collection creates a new snapshot.

package com.example.javersdemo

import org.javers.core.Javers
import org.javers.repository.jql.QueryBuilder
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import spock.lang.Specification

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class JaversInnerValueObjectsTest extends Specification {

    @Autowired
    TestUserRepository userRepository

    @Autowired
    Javers javers


    def 'should create 6 snapshots'() {

        given:
        def hobbies = [new Hobby('Reading books', true)]
        def user = new User('John', hobbies)

        when:
        userRepository.save(user)

        hobbies = [new Hobby('Reading books', true), new Hobby('Watching Soccer', true)]

        user.hobbies = hobbies

        userRepository.save(user)

        hobbies = [new Hobby('Reading books', true), new Hobby('Watching Soccer', false)]

        user.hobbies = hobbies

        userRepository.save(user)



        then:
        QueryBuilder jqlQuery = QueryBuilder.byInstanceId('John', User)
        def snapshots = javers.findSnapshots(jqlQuery.withChildValueObjects().build())
        snapshots.size() == 6


    }

}

and the test fails with below error

Condition not satisfied:

snapshots.size() == 6 | | | | 5 false [Snapshot{commit:3.0, id:User/John#hobbies/1, version:2, (hobby:Watching Soccer)}, Snapshot{commit:2.0, id:User/John#hobbies/1, version:1, (active:true, hobby:Watching Soccer)}, Snapshot{commit:2.0, id:User/John, version:2, (hobbies:[User/John#hobbies/0, User/John#hobbies/1], name:John)}, Snapshot{commit:1.0, id:User/John#hobbies/0, version:1, (active:true, hobby:Reading books)}, Snapshot{commit:1.0, id:User/John, version:1, (hobbies:[User/John#hobbies/0], name:John)}]

Expected :6

Actual :5

Swamy
  • 21
  • 4

1 Answers1

0

In the log there are JaVers commit statistis:

13:27:44.315 [main] INFO  org.javers.core.Javers - Commit(id:1.0, snapshots:2, author:author, changes - NewObject:2), done in 71 millis (diff:71, persist:0)
13:27:44.333 [main] INFO  org.javers.core.Javers - Commit(id:2.0, snapshots:2, author:author, changes - ListChange:1 NewObject:1), done in 17 millis (diff:17, persist:0)
13:27:44.336 [main] INFO  org.javers.core.Javers - Commit(id:3.0, snapshots:1, author:author, changes - ValueChange:1), done in 3 millis (diff:3, persist:0)

In the firts commit, 2 snapshots are created because we have 2 new objects here.

In the second commit another 2 snapshots are created:

  • User (entity) snapshot, because user's field hobbies is changed - new item added to the list so ListChange
  • Hobby (value Object) snapshot, because it's a new object

In the third commit only 1 snapshot is created, because user's field hobbies isn't changed. JaVers treats collections of Value Objects and collections of Entities as collections of references (GlobalId's). In this case, references are not changed. The collection's state is captured as follows:

["User/John#hobbies/0",
 "User/John#hobbies/1"]

The only change in the third commit is the Hobby's field change (true->false) so ValueChange in ValueObject with GlobalId User/John#hobbies/1

In other words, when you change Value Objects owned by some Entity, and there is no change in Entity state, Entity snapshot will not be created, because it would be exactly the same as the previous one.

Bartek Walacik
  • 3,386
  • 1
  • 9
  • 14
  • thanks @BartekWalacik . Javers in its current form doesn't suit our use case. Value object in my example has no significance without enclosing entity, i would like to see changes in value objects being part of the entity snapshot, May be in order to collect the collection state the value object version could be added along with it's id like `User/John#hobbies/1#v1` – Swamy Jun 13 '17 at 22:47
  • Did you tried the new Shadows? Shadows strive to restore original object graph. http://javers.org/documentation/jql-examples/#query-for-shadows – Bartek Walacik Jun 14 '17 at 08:36
  • Can't we directly have the entire entity when storing it as a snapshot instead of having separate valueObjects ? – Kavindu Vindika Aug 23 '21 at 11:04