Рубрики
Без рубрики

Использование JaVers для аудита моделей данных в весенних данных

Узнайте, как использовать JaVers для аудита моделей данных в весенних данных.

Автор оригинала: baeldung.

Использование JaVers для аудита моделей данных в весенних данных

1. Обзор

В этом учебнике мы увидим, как настроить и использовать JaVers в простом приложении Spring Boot для отслеживания изменений сущностей.

2. ДжаВерс

При работе с мутируемыми данными у нас обычно есть только последнее состояние сущности, хранящееся в базе данных. Как разработчики, мы тратим много времени на отладку приложения, поиск файлов журнала для события, которое изменило состояние. Это становится еще сложнее в производственной среде, когда много различных пользователей используют систему.

К счастью, у нас есть большие инструменты, как JaVers . JaVers – это структура журнала аудита, которая помогает отслеживать изменения сущностей в приложении.

Использование этого инструмента не ограничивается только отладкой и аудитом. Он может быть успешно применен для выполнения анализа, политики силы безопасности и поддержания журнала событий, тоже.

3. Настройка проекта

Прежде всего, чтобы начать использовать JaVers, нам нужно настроить репозиторий аудита для сохраняющихся снимков сущностей. Во-вторых, нам нужно настроить некоторые настраиваемые свойства JaVers. Наконец, мы также покроем, как правильно настроить наши модели доменов.

Но, стоит отметить, что JaVers предоставляет параметры конфигурации по умолчанию, так что мы можем начать использовать его практически без конфигурации.

3.1. Зависимости

Во-первых, мы должны добавить JaVers Spring Boot стартовой зависимости от нашего проекта. В зависимости от типа хранения настойчивости у нас есть два варианта: org.javers:javers-весна-загрузка-стартер-sql и org.javers:javers-весна-загрузка-стартер-монго . В этом учебнике мы будем использовать стартер Spring Boot S’L.


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

Как мы собираемся использовать базу данных H2, давайте также включить эту зависимость:


    com.h2database
    h2

3.2. Настройка репозитория JaVers

JaVers использует абстракцию репозитория для хранения коммитов и сериализованных сущностей. Все данные хранятся в формате JSON. Таким образом, это может быть хорошо подходит для использования хранения NoS’L. Однако, ради простоты, мы будем использовать экземпляр памяти H2.

По умолчанию JaVers использует реализацию репозитория памяти, и если мы используем Spring Boot, нет необходимости в дополнительной конфигурации. Кроме того, при использовании Весенние данные во-первых, JaVers повторно использовать конфигурацию базы данных для .

JaVers предоставляет два стартера для стеков настойчивости S’L и Mongo.  Они совместимы с Spring Data и не требуют дополнительной конфигурации по умолчанию. Тем не менее, мы всегда можем переопределить фасоль конфигурации по умолчанию: JaversSqlAutoConfiguration.java и JaversMongoAutoConfiguration.java соответственно.

3.3. Свойства JaVers

JaVers позволяет настроить несколько вариантов, хотя и Весенняя загрузка по умолчанию достаточны в большинстве случаев использования.

Давайте переопределим только один, newObjectSnapshot , так что мы можем получить снимки вновь созданных объектов:

javers.newObjectSnapshot=true

3.4. Конфигурация домена JaVers

JaVers внутренне определяет следующие типы: Сущности, Объекты ценностей, Значения, Контейнеры и Примитивы. Некоторые из этих терминов приходят от терминологии DDD (Домен Driven Design).

Основная цель нескольких типов заключается в предоставлении различных алгоритмов дифф в зависимости от типа . Каждый тип имеет соответствующую стратегию дифф. Как следствие, если классы приложений настроены неправильно, мы получим непредсказуемые результаты.

Чтобы сказать JaVers, какой тип использовать для класса, у нас есть несколько вариантов:

  • Явно – Первый вариант заключается в явном использовании регистрировать» методы JaversBuilder класс – второй способ заключается в использовании аннотаций
  • Неявно – JaVers предоставляет алгоритмы для автоматического обнаружения типов на основе классовых отношений
  • По умолчанию – по умолчанию JaVers будет относиться ко всем классам как к ЗначениеОбъекты

В этом учебнике мы настроили JaVers явно, используя метод аннотации.

Самое замечательное, что JaVers совместим с javax.persistence аннотации . В результате нам не нужно будет использовать аннотации JaVers на наших сущностях.

4. Пример проекта

Теперь мы собираемся создать простое приложение, которое будет включать в себя несколько доменных сущностей, которые мы будем аудита.

4.1. Модели доменов

Наш домен будет включать в себя магазины с продуктами.

Давайте определим Магазин сущность:

@Entity
public class Store {

    @Id
    @GeneratedValue
    private int id;
    private String name;

    @Embedded
    private Address address;

    @OneToMany(
      mappedBy = "store",
      cascade = CascadeType.ALL,
      orphanRemoval = true
    )
    private List products = new ArrayList<>();
    
    // constructors, getters, setters
}

Обратите внимание, что мы используем аннотации JPA по умолчанию. JaVers отображает их следующим образом:

  • @javax.persistence.Entity отображается на @org.javers.core.metamodel.annotation.Entity
  • @javax.persistence.Embeddable отображается на @org.javers.core.metamodel.annotation.ValueObject.

Встраиваемые классы определяются обычным способом:

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

4.2. Хранилища данных

Для аудита репозиториев JPA JaVers предоставляет @JaversSpringDataAuditable аннотация.

Давайте определим МагазинРепозиторий с этой аннотации:

@JaversSpringDataAuditable
public interface StoreRepository extends CrudRepository {
}

Кроме того, мы будем иметь ПродуктРепозиторий , но не аннотированный:

public interface ProductRepository extends CrudRepository {
}

Теперь рассмотрим случай, когда мы не используем хранилища весенних данных. JaVers имеет другую аннотацию уровня метода для этой цели: @JaversAuditable.

Например, мы можем определить метод для химинга продукта следующим образом:

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

Кроме того, мы можем даже добавить эту аннотацию непосредственно над методом в интерфейсе репозитория:

public interface ProductRepository extends CrudRepository {
    @Override
    @JaversAuditable
     S save(S s);
}

4.3. Поставщик авторских услуг

Каждое совершенное изменение в JaVers должно иметь своего автора. Кроме того, JaVers поддерживает Spring Security из коробки.

В результате каждый коммит совершается определенным аутентифицированным пользователем. Тем не менее, для этого учебника мы создадим действительно простую пользовательскую реализацию АвторПровинидер интерфейс:

private static class SimpleAuthorProvider implements AuthorProvider {
    @Override
    public String provide() {
        return "Baeldung Author";
    }
}

И в качестве последнего шага, чтобы jaVers использовать нашу пользовательскую реализацию, мы должны переопределить конфигурацию по умолчанию фасоли:

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

5. Аудит JaVers

Наконец, мы готовы провести аудит нашей заявки. Мы будем использовать простой контроллер для отправки изменений в наше приложение и получения журнала коммитов JaVers. Кроме того, мы также можем получить доступ к консоли H2, чтобы увидеть внутреннюю структуру нашей базы данных:

Чтобы иметь некоторые исходные данные выборки, давайте использовать EventListener заполнить нашу базу данных с некоторыми продуктами:

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

5.1. Первоначальный коммит

Когда объект создается, JaVers сначала совершает НАЧАЛЬНАЯ тип .

Давайте проверим снимки после запуска приложения:

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

В коде выше, мы запрашиваем JaVers для снимков для Магазин класс. Если мы сделаем запрос на эту конечную точку мы получим результат, как один ниже:

[
  {
    "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": "address"
      },
      "name": "Baeldung store",
      "id": 1,
      "products": [
        {
          "entity": "com.baeldung.springjavers.domain.Product",
          "cdoId": 2
        },
        {
          "entity": "com.baeldung.springjavers.domain.Product",
          "cdoId": 3
        }
      ]
    },
    "changedProperties": [
      "address",
      "name",
      "id",
      "products"
    ],
    "type": "INITIAL",
    "version": 1
  }
]

Обратите внимание, что снимок выше включает в себя все продукты, добавленные в магазин, несмотря на недостающие аннотации для ПродуктРепозиторий интерфейс .

По умолчанию JaVers будет проводить аудит всех связанных моделей совокупного корня, если они сохраняются вместе с родительским.

Мы можем сказать JaVers игнорировать определенные классы, используя ДиффИнор аннотация.

Например, мы можем аннотировать продукты поле с аннотацией в Магазин сущность:

@DiffIgnore
private List products = new ArrayList<>();

Следовательно, JaVers не будет отслеживать изменения продуктов, происходящих из Магазин сущность.

5.2. Обязательство обновления

Следующим типом коммит является ОБНОВЛЕНИЕ совершать. Это наиболее ценный тип коммитов, поскольку он представляет собой изменение состояния объекта.

Давайте определим метод, который будет обновлять сущность магазина и все продукты в магазине:

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

Если мы забудем этот метод, мы получим следующую строку в выходе отладки (в случае того же продукта и магазины рассчитывать):

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

Так как JaVers успешно сохраняет изменения, давайте запросить снимки для продуктов:

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

Мы получим предыдущие НАЧАЛЬНАЯ коммиты и новые ОБНОВЛЕНИЕ Совершает:

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

Здесь мы можем увидеть всю информацию об изменениях, которые мы сделали.

Стоит отметить, что JaVers не создает новых подключений к базе данных. Вместо этого он повторно . Данные JaVers транзакционные или откатываются вместе с данными приложения в той же транзакции.

5.3. Изменения

JaVers записывает изменения как атомные различия между версиями объекта . Как мы видим из схемы JaVers, отдельной таблицы для хранения изменений нет, поэтому JaVers вычисляет изменения динамически, так как разница между моментальными .

Давайте обновим цену продукта:

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

Затем давайте запросим JaVers для внесения изменений:

@GetMapping("/products/{productId}/changes")
public String getProductChanges(@PathVariable int productId) {
    Product product = storeService.findProductById(productId);
    QueryBuilder jqlQuery = QueryBuilder.byInstance(product);
    Changes changes = javers.findChanges(jqlQuery.build());
    return javers.getJsonConverter().toJson(changes);
}

Выход содержит измененное свойство и его значения до и после:

[
  {
    "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
    },
    "property": "price",
    "propertyChangeType": "PROPERTY_VALUE_CHANGED",
    "left": 100.0,
    "right": 3333.0
  }
]

Для обнаружения типа изменения JaVers сравнивает последующие снимки обновлений объекта. В случае выше, как мы изменили свойство лица у нас есть PROPERTY_VALUE_CHANGED изменить тип.

5.4. Тени

Кроме того, JaVers предоставляет другое представление о проверенных организациях, называемых Теневой . Тень представляет собой состояние объекта, восстановленное на снимках. Эта концепция тесно связана с Событие Sourcing .

Есть четыре различных области для теней:

  • Мелкие — тени создаются на основе снимка, выбранного в запросе J’L
  • Значение ребенка-объект – тени содержат все объекты детской ценности, принадлежащие выбранным объектам
  • По-прежнему – тени создаются из всех снимков, связанных с выбранными сущностями
  • Глубокое – JaVers пытается восстановить полный график объектов с (возможно) все объекты загружены.

Давайте использовать область объекта «Ребенок-значение» и получим тень для одного магазина:

@GetMapping("/stores/{storeId}/shadows")
public String getStoreShadows(@PathVariable int storeId) {
    Store store = storeService.findStoreById(storeId);
    JqlQuery jqlQuery = QueryBuilder.byInstance(store)
      .withChildValueObjects().build();
    List> shadows = javers.findShadows(jqlQuery);
    return javers.getJsonConverter().toJson(shadows.get(0));
}

В результате, мы получим объект магазина с Адрес значение объекта:

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

Чтобы получить продукты в результате, мы можем применить область Commit-deep.

6. Заключение

В этом учебнике мы видели, как легко JaVers интегрируется с Spring Boot и Spring Data, в частности. В целом, JaVers требует почти нулевой конфигурации для настройки.

В заключение, JaVers может иметь различные приложения, от отладки до сложного анализа.

Полный проект этой статьи доступен более на GitHub .