Complete CQRS Application with Axon Framework – 1 (Step by Step Guide)

Complete CQRS Application with Axon Framework

We would build a complete CQRS application with Axon Framework. I would help you understand the various components that make up a CQRS application. This would be done in two parts:

  • build the API
  • build the UI

This would be a simple eCommerce application that allows users to place orders. Then when an order is placed, the product stock is updated. In this part we would cover:

  1. The Transaction Steps
  2. The Read and Write Models
  3. The Commands and Events
  4. Watch the Video

Before we proceed, let’s present the logical flow of the application.

 

1. The Transaction Steps
  • A user clicks on the the “Order Now!” button in the UI from a browser
  • A REST/JSON request is raised and is routed to the controller
  • The orderController creates a new order command and sends it via the command gateway (onto the command bus)
  • The command is routed to the command handler (by the command bus)
  • The order command handler creates a  new order based on the details it received (this is event-sourced)
  • When the new order is created, it is persisted in the Write DB via the orderRepository
  • The repository insert emits an order created event
  • Then using the productId, we retrieve the particular Product aggregate from the Repository (org.axonframework.modelling.command.Repository)
  • This retrieved product then executes its updateStock using a new UpdateStockCommand as parameter
  • At this point, the stock is deprecated. This change is effected in the repository and persisted in the Write DB
  • This repository changes, fires a StockUpdated event
  • The event handler for StockUpdated event receives this event together with the details
  • Then the event handler, makes the necessary update in the Read model as well

Let’s now examine the various code components of this workflow

 

2. The Read and Write Models

As you know, in a CQRS architecture, there are two models.

the Read Model: the normal entity which we always used. A java class annotated with @Entity annotation and represents objects to the stored in the database. This model interface with the data store by extending the import org.springframework.data.jpa.repository.JpaRepository (or CrudRepository).

In this demo, we call the read model for the product, ProductSummary and the code is given below.

@Entity
@Data
@NoArgsConstructor
public class ProductSummary {
    @Id
    private String  id;
    private Double price;
    private Integer stock;
    private String description;

    public ProductSummary(String id, Double price, Integer stock, String description) {
        this.id = id;
        this.price = price;
        this.stock = stock;
        this.description = description;
    }
}

As for the Order, get it from my github repo here.

 

the Write Model: basically same thing with some differences. However, this is called an aggregate and annotated with @Aggregate annotation. This model interfaces with the data store through a repository that extends org.axonframework.modelling.command.Repository.

In this demo, the write model is called Product as given below.

@Aggregate
public class Product {
    @AggregateIdentifier
    private String id;
    private Double price;
    private Integer stock;
    private String description;

    public Product() {
    }

    @CommandHandler
    public Product(AddProductCommand cmd){
        apply( new ProductAddedEvent(
                cmd.getId(),
                cmd.getPrice(),
                cmd.getStock(),
                cmd.getDescription()
                )
        );
    }

    @CommandHandler
    public void updateStock (UpdateStockCommand cmd){
        if(this.stock >= cmd.getStock()){
            apply(new StockUpdatedEvent(
                    cmd.getId(), 
                    cmd.getStock())
            );
        }
        else {
            throw new RuntimeException("Out of Stock!");
        }
    }

    @EventSourcingHandler
    public void on(StockUpdatedEvent evt){
        id = evt.getId();
        stock = stock - evt.getStock();
    }

    @EventSourcingHandler
    public void on(ProductAddedEvent evt){
        id = evt.getId();
        price = evt.getPrice();
        stock = evt.getStock();
        description = evt.getDescription();
    }
}

 

3. The Commands and Events

In CQRS terms, a command is an expression of intent to perform certain operation while an event is a notification that an operation has occurred. So in our Ecommerce demo application, what commands can you think of? How about this:

CreateOrder: that is to place a new order for a product

UpdateStock: deduct the number of product from the stock when an order is placed

AddProduct: add a new product to the stock

Note that, for each command, there is a corresponding event.  For this demo, I have placed the commands and events inside the api package. The three commands are placed inside  the commands.kt kotlin file while the events are placed inside the events.kt file. This is shown below:

data class CreateOrderCommand (
        @TargetAggregateIdentifier
        val orderId: String,
        val price: Double,
        val number:Int,
        val productId:String
)

data class AddProductCommand (
        @TargetAggregateIdentifier
        val id: String,
        val price: Double,
        val stock:Int,
        val description: String
)

data class  UpdateStockCommand(
        @TargetAggregateIdentifier
        val id: String,
        val stock: Int
)

 

The events are given below:

data class OrderCreatedEvent (
        val orderId: String,
        val price: Double,
        val number:Int,
        val productId:String
)

data class ProductAddedEvent (
        val id: String,
        val price: Double,
        val stock:Int,
        val description: String
)

data class StockUpdatedEvent(
        val id:String,
        val stock: Int
)

 

What’s Left?

So next we would have to define the event handlers. Then we also create the controller file that receives request from the client. This is covered in the next part!