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

CQRS с использованием Java и Axon – командного модуля

Вступление Во второй части этой серии статей мы реализуем Командный мод… С тегами java, учебник, веб-разработчик, архитектура.

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

Мы эффективно начнем кодировать наше приложение с внешнего уровня и продолжим работать внутри. Класс ProductController будет отвечать за предоставление конечным точкам возможности запрашивать изменения состояния.

Единственной зависимостью этого класса будет зависимость Аксона Командный шлюз ответственный за отправку команды объекты. Первоначальная структура будет:

@RestController
@RequestMapping("/products")
public class ProductController {

    @Autowired
    public ProductController(final CommandGateway commandGateway) {
        this.commandGateway = commandGateway;
    }

    private CommandGateway commandGateway;

    @PostMapping
    public CompletableFuture create(@RequestBody ProductDTO dto) {
        return null;
    }

    @PutMapping
    public CompletableFuture update(@RequestBody ProductDTO dto) {
        return null;
    }
}

Где ProductDTO класс – это просто POJO для отображения запроса json .

public class ProductDTO {

    private Long id;

    private String name;

    private int quantity;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getQuantity() {
        return quantity;
    }

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }
}

Экземпляры команды в этом приложении будут представлять намерения изменения состояния. Подмножество состояния приложения будет представлено объектом Aggregate . Например, намерение добавить новый товар в корзину представлено Добавить команду продукта класс:

public class AddProductCommand {

    public AddProductCommand(
            final Long id,
            final String name,
            final int quantity) {
        this.id = id;
        this.name = name;
        this.quantity = quantity;
    }

    @TargetAggregateIdentifier
    private final Long id;
    private final String name;
    private final int quantity;

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public int getQuantity() {
        return quantity;
    }
}

Где Целевой агрегатный идентификатор аннотация используется для определения того, какой экземпляр Агрегатного типа должен обрабатываться этой командой.

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

@RestController
@RequestMapping("/products")
public class ProductController {

    @Autowired
    public ProductController(final CommandGateway commandGateway) {
        this.commandGateway = commandGateway;
    }

    private CommandGateway commandGateway;

    @PostMapping
    public CompletableFuture create(@RequestBody ProductDTO dto) {
        AddProductCommand command = new AddProductCommand(
                dto.getId(),
                dto.getName(),
                dto.getQuantity());
        return commandGateway.send(command);
    }

    // ....
}

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

Примером Агрегата может быть:

{
  "id": 1,
  "name": "iPhone",
  "quantity": 2
}

Перевод в код приводит к:

@Aggregate
public class ProductAggregate {

    @AggregateIdentifier
    private Long id;
    private String name;
    private int quantity;

    @CommandHandler
    public ProductAggregate(AddProductCommand cmd) {
        // Verifies state consistency and applies events
    }
}

Обработчик команд аннотация на конструкторе означает Команда Добавить продукт команда используется для создания нового Совокупный .

На данный момент состояние вашего приложения еще не изменилось. Обработка команд – это место для выполнения бизнес-логики (т. Е.: проверьте, является ли количество положительным значением) и, возможно, примените События , которые приведут к изменению состояния.

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

Теперь мы изменим Совокупность продуктов |/конструктор для отправки Добавить событие всякий раз, когда Отправляется команда Добавить продукт . Этот же класс также может выступать в качестве обработчика источника событий AddProductEvent , выполняющий изменение состояния приложения.

import static org.axonframework.modelling.command.AggregateLifecycle.apply;


@Aggregate
public class ProductAggregate {

    @AggregateIdentifier
    private Long id;
    private String name;
    private int quantity;

    @CommandHandler
    public ProductAggregate(AddProductCommand cmd) {
        apply(new AddProductEvent(cmd.getId(), cmd.getName(), cmd.getQuantity()));
    }

    @EventSourcingHandler
    public void on(AddProductEvent event) {
        this.id = event.getId();
        this.name = event.getName();
        this.quantity = event.getQuantity();
    }
}

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

docker-compose up -d
./gradlew clean assemble
java -jar commandside/build/libs/commandside.jar

Теперь мы можем протестировать конечную точку, добавив новый продукт:

curl -X POST http://localhost:8080/products -H 'Content-Type: application/json' -d '{"id": 1, "name": "iPhone", "quantity": 7}'

Мы можем проверить в базе данных mongo, в событиях домена коллекции, что там хранится новое событие:

{
   "_id":"5e0a2924b813b63783e1e092",
   "aggregateIdentifier":"1",
   "type":"ProductAggregate",
   "sequenceNumber":"0",
   "serializedPayload":"1iPhone7",
   "timestamp":"2019-12-30T16:43:16.851862731Z",
   "payloadType":"com.example.project.command.addproduct.AddProductEvent",
   "payloadRevision":null,
   "serializedMetaData":"traceIde62c8e0d-7505-4e99-ab7e-84b4619ee159correlationIde62c8e0d-7505-4e99-ab7e-84b4619ee159",
   "eventIdentifier":"6eef19d8-b22a-4be6-9fd9-7681a31580b8"
}

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

Помимо сохранения отдельных событий, мы также хотим сохранять совокупность при каждом изменении (совокупность, хранящаяся в состоянии). Для достижения этой цели нам просто нужно добавить аннотации JPA, чтобы превратить наш совокупный класс в Сущность :

@Aggregate
@Entity // This class can now be mapped to a table 
public class ProductAggregate {

    @AggregateIdentifier
    @Id // Defines the primary key
    private Long id;

    @Column // Map to a column with same name
    private String name;

    @Column // Map to a column with same name
    private int quantity;

    @CommandHandler
    public ProductAggregate(AddProductCommand cmd) {
        apply(new AddProductEvent(cmd.getId(), cmd.getName(), cmd.getQuantity()));
    }

    @EventSourcingHandler
    public void on(AddProductEvent event) {
        this.id = event.getId();
        this.name = event.getName();
        this.quantity = event.getQuantity();
    }
}

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

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

Мы могли бы вернуться назад и реализовать CQR и поиск событий самостоятельно, но, к счастью, мы можем достичь того же результата, используя пару аннотаций от Axon.

Оригинал: “https://dev.to/fabiothiroki/cqrs-using-java-and-axon-command-module-57h5”