Лучшая практика для предотвращения повторяющихся событий AggregateCreated - PullRequest
0 голосов
/ 07 ноября 2018

У меня есть следующий (Аксон) Совокупный:

@Aggregate
@NoArgsConstructor
public class Car{
  @AggregateIdentifier
  private String id;

  @CommandHandler
  public Car(CreateCar command){
    apply( new CarCreated(command.getId()) );
  }

  @EventSourcingHandler
  public void carCreated(CarCreated event) {
    this.id = event.getId();
  }

}

И я могу создать автомобиль, отправив команду CreateCar с определенным идентификатором, вызвав событие CarCreated. Это здорово.

Однако, если я отправлю другую команду CreateCar с тем же Id, команда не может быть проверена агрегатом (что данный идентификатор уже существует). Впоследствии он просто запустит новое событие CarCreated. Который является ложью.

Как лучше всего убедиться в сбое команды CreateCar, если автомобиль уже существует?

Естественно, я мог бы сначала проверить хранилище, но это не помешает условиям гонки ...

Ответы [ 2 ]

0 голосов
/ 12 ноября 2018

Однако, если я отправлю другую команду CreateCar с тем же идентификатором, команда не может быть проверена агрегатом (что данный идентификатор уже существует). Впоследствии он просто запустит новое событие CarCreated. Который является ложью.

Аксон позаботится об этом за вас. Когда агрегат публикует событие, оно не публикуется сразу для других компонентов. Он размещается в модуле работы, ожидая завершения выполнения обработчика. После выполнения обработчика вызывается ряд обработчиков «prepare commit». Один из них хранит агрегат (который не используется при использовании источников событий), другой - публикация событий (в рамках транзакции).

В зависимости от того, используете ли вы Event Sourcing или нет, добавление экземпляра Aggregate в постоянное хранилище завершится неудачно (дублирующий ключ) или публикация события создания завершится неудачно (дублирующий идентификатор агрегата + порядковый номер).

0 голосов
/ 07 ноября 2018

Как лучше всего убедиться, что команда CreateCar не работает, если автомобиль уже существует? Естественно, я мог бы сначала проверить хранилище, но это не помешает условиям гонки ...

Магии нет.

Если вы собираетесь избежать колоссальных записей, вам нужно либо получить блокировку хранилища данных, либо хранилище данных с семантикой compare and swap.

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

lock = lock_for_id id
lock.acquire
Try:
    Option[Car] root = repository.load id
    switch root {

        case None:
            Car car = createCar ...
            repository.store car  

        case Some(car):
            // deal with the fact that the car has already been created   

    }
Finally:
    lock.release

Вы хотели бы иметь блокировку для каждого агрегата, но создание блокировок имеет те же условия, что и создание агрегатов. Таким образом, вы, скорее всего, получите что-то вроде грубозернистого замка для ограничения доступа к операции.

С помощью функции сравнения и обмена вы подталкиваете управление конфликтами к хранилищу данных. Вместо отправки в магазин PUT , вы отправляете условный PUT .

    Option[Car] root = repository.load id
    switch root {

        case None:
            Car car = createCar ...
            repository.replace car None

        case Some(car):
            // deal with the fact that the car has already been created   

    }

Нам больше не нужны блокировки, потому что мы точно описываем для магазина предварительное условие (например, If-None-Match: *), которое должно быть удовлетворено.

Семантика сравнения и обмена обычно поддерживается хранилищами событий; «добавление» новых событий в поток выполняется путем создания запроса, который идентифицирует ожидаемую позицию указателя хвоста, со специально закодированными значениями для определения случаев, когда ожидается создание потока (например, Event Store поддерживает ExpectedVersion.NoStream семантическая).

...