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 SAGA Tutorial :
- Download and Run Axon Server
- CQRS Architecture Overview
- Spring Boot CQRS Example
- Spring Boot CQRS - Query Example
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
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.
- The
CommandGateway
allows us to deliver commands to Command Handler. 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.
@AggregateIdentifier
annotation signify that the class is Aggregator class.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
Refer Query Part of Spring Boot CQRS Example.
Test the Complete CQRS Microservice
- Prerequisite : Run the Axon Server as per the steps given here.
- Start the Spring Boot Application by running
spring-boot:run
or by running main class. POST - To test Command Query
Open POSTMAN, use the rest endpoint as http://localhost:8080/employee and provide the employee details in the body.GET - To test Query part
Open POSTMAN, use the rest endpoint as http://localhost:8080/employees and click on Send button.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.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.
Download Source Code
The full source code for this article can be found on below.
Download it here -
Spring
Boot CQRS Example