Spring Boot CQRS Example (2024) | TechGeekNext

Spring Boot CQRS Example (2024)

In this tutorial, we will explore the how to implement the CQRS (Command Query Responsibility Segregation) into our Spring Boot application with Axon Framework and Axon Server as an event store.

CQRS also utilised in many microservices as a suitable design pattern to follow when you have to operate or separate read and write operations.

What is CQRS?
Ans:

CQRS (Command Query Responsibility Segregation) is a microservices design pattern that divides reading and writing into two models. Every method should be either a Command that performs an action or a Query that returns data. CQRS divides reads and writes into independent models, updating data with commands and reading data with queries.

The name Command Query Responsibility Segregation denotes that Command is used to handle POST, PUT, PATCH, DELETE - (WRITE) operations, whilst Query is used to handle GET - (READ) operation.

CQRS Command operation Flow

Spring Boot CQRS Command Part

Axon Framework that adheres to Domain Driven Design (DDD) principles supports microservices patterns with such as Command-Query-Responsibility-Segregation (CQRS) and Event-Driven Architecture in addition to DDD.

For creating DDD, CQRS, and Event Sourcing applications, the open source Axon Framework provides a clear, attractive Java API. It includes basic building blocks for creating aggregates, commands, queries, events, sagas, command handlers, event handlers, query handlers, repositories, and communication buses, among other things.

Axon Dependency

Add axon-spring-boot-starter for CQRS and other dependencies required for project.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.5.8</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.techgeeknext</groupId>
	<artifactId>spring-boot-cqrs-example</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-boot-cqrs-example</name>
	<description>Spring Boot CQRS Example</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>


		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
			<version>30.1-jre</version>
		</dependency>


		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>


		<dependency>
			<groupId>org.axonframework</groupId>
			<artifactId>axon-spring-boot-starter</artifactId>
			<version>4.4.7</version>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

Rest Controller

Create the rest controller class to handle the POST endpoint and create Employee Data.

  1. The CommandGateway allows us to deliver commands to Command Handler.
  2. sendAndWait(Object command) sends and waits for the provided command to be executed. When the result of the execution is available, it is returned.
package com.techgeeknext.command.controller;

import org.axonframework.commandhandling.gateway.CommandGateway;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import java.util.UUID;

@RestController
public class EmployeeCommandController {

    @Autowired
    private CommandGateway commandGateway;

    @PostMapping("/employee")
        public String newEmployee(@Valid @RequestBody EmployeeRequest employee) {

        CreateEmployeeCommand newEmployee = CreateEmployeeCommand.builder()
                .uid(UUID.randomUUID().toString())
                .name(employee.getName())
                .address(employee.getAddress())
                .role(employee.getRole())
                .status("CREATED")
                .build();

        try {
            return commandGateway.sendAndWait(newEmployee);
        }catch (Exception exception){
            return exception.getMessage();
        }
    }
}

Take a look at our suggested posts:

Request Model Object

package com.techgeeknext.command.controller;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class EmployeeRequest {

    private String name;
    private String address;
    private String role;
}

Employee Created Command Event

The @TargetAggregateIdentifier annotation informs Axon that the annotated field is an identifier for a specific aggregate to which the command should be sent.

package com.techgeeknext.command.controller;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.axonframework.modelling.command.TargetAggregateIdentifier;

@Data
@AllArgsConstructor
@Builder
public class CreateEmployeeCommand {

    @TargetAggregateIdentifier
    private String uid;
    private String name;
    private String address;
    private String role;
    private String status;
}

Event Command Handler

What is Aggregator?
Ans:

An aggregate is a self-contained tree of objects capable of processing commands. Event Sourcing used to implement aggregates in Axon. An @AggregateIdentifier field must be present in an event source to aggregate.

  1. @AggregateIdentifier annotation signify that the class is Aggregator class.
  2. AggregateLifecycle.apply, invokes event source handler method (employeeEvent) with employee created event.
package com.techgeeknext.command.aggregator;

import com.techgeeknext.command.controller.CreateEmployeeCommand;
import com.techgeeknext.command.event.EmployeeCreatedEvent;
import org.axonframework.commandhandling.CommandHandler;
import org.axonframework.eventsourcing.EventSourcingHandler;
import org.axonframework.modelling.command.AggregateIdentifier;
import org.axonframework.modelling.command.AggregateLifecycle;
import org.axonframework.spring.stereotype.Aggregate;
import org.springframework.beans.BeanUtils;

@Aggregate
public class EmployeeAggregate {

    //Unique identifier to aggregate
    @AggregateIdentifier
    private String uid;
    private String name;
    private String address;
    private String role;

    public EmployeeAggregate() {
    }

    /**
     * Command Handler method to create employee object
     *
     * @param createEmployeeCommand
     */
    @CommandHandler
    public EmployeeAggregate(CreateEmployeeCommand createEmployeeCommand) {
        //Here we can handle validation logic on createEmployeeCommand object
        EmployeeCreatedEvent employeeCreatedEvent = new EmployeeCreatedEvent();
        BeanUtils.copyProperties(createEmployeeCommand, employeeCreatedEvent);
        //it invokes event source handler method (employeeEvent) with employee created event
        AggregateLifecycle.apply(employeeCreatedEvent);
    }

    /**
     * Method to store the EmployeeCreatedEvent in the eventstore
     *
     * @param employeeCreatedEvent
     */
    @EventSourcingHandler
    public void employeeEvent(EmployeeCreatedEvent employeeCreatedEvent) {
        this.uid = employeeCreatedEvent.getUid();
        this.name = employeeCreatedEvent.getName();
        this.address = employeeCreatedEvent.getAddress();
        this.role = employeeCreatedEvent.getRole();

    }
}

JPA Repository

package com.techgeeknext.repository;

import com.techgeeknext.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;

public interface EmployeeRepository extends JpaRepository<Employee, Long> {

}

Store Events in Database

package com.techgeeknext.command.eventhandler;

import com.techgeeknext.command.event.EmployeeCreatedEvent;
import com.techgeeknext.entity.Employee;
import com.techgeeknext.repository.EmployeeRepository;
import org.axonframework.eventhandling.EventHandler;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class EmployeeEventHandler {

    @Autowired
    private EmployeeRepository employeeRepository;

    @EventHandler
    public void employeCretedEvent(EmployeeCreatedEvent employeeCreatedEvent){
        Employee emp = new Employee();
        BeanUtils.copyProperties(employeeCreatedEvent, emp);
        employeeRepository.save(emp);
    }
}

Employee Entity

package com.techgeeknext.entity;

import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;
@Entity
@Table
@Data
public class Employee implements Serializable {

    private static final long serialVersionUID = -2373578151904440373L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long employeeId;

    @Column(unique = true)
    private String uid;

    private String name;

    private String address;

    private String role;
}

Database Connection Properties

Add below H2 database properties in application.properties file.

spring.application.name=employee-cqrs-service

spring.datasource.url=jdbc:h2:file:~/employeetestdb;AUTO_SERVER=true
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
spring.h2.console.enabled=true
spring.h2.console.settings.web-allow-others=true

server.error.include-message=always
server.error.include-binding-errors=always

CQRS Query operation Flow

Spring Boot CQRS Query Part

Refer Query Part of Spring Boot CQRS Example.

Test the Complete CQRS Microservice

  1. Prerequisite : Run the Axon Server as per the steps given here.
  2. Start the Spring Boot Application by running spring-boot:run or by running main class.
  3. POST - To test Command Query
    Open POSTMAN, use the rest endpoint as http://localhost:8080/employee and provide the employee details in the body. Spring Boot CQRS Command Output
  4. GET - To test Query part
    Open POSTMAN, use the rest endpoint as http://localhost:8080/employees and click on Send button. Spring Boot CQRS Query Output
  5. AXON Dashboard
    Once Axon Server started, goto to axon dashboard on http://localhost:8888/ and click on Search -> aggregateIdentifier = "5ad2b608-b22e-4f4a-bd99-79260706eadb" (for more details click on About the query language) -> Click on Search. Spring Boot CQRS Query Output
  6. Database tables
    Once we run the Spring Boot application CQRS - SAGA related tables would get created and with POST rest endpoint employee data gets stored in the table. Open the h2 console in web browser at http://localhost:8080/h2-console. Spring Boot CQRS Query Output Spring Boot CQRS Query Output

Download Source Code

The full source code for this article can be found on below.
Download it here - Spring Boot CQRS Example


Recommendation for Top Popular Post :