Java Microservices - Command Query Responsibility Segregation (CQRS)



Introduction

In traditional CRUD-based applications, the same data model is used to perform both read and write operations. While simple and effective for smaller systems, this model introduces limitations as applications scale in size, complexity, and performance demands.

Command Query Responsibility Segregation (CQRS) is a design pattern that separates the read (query) and write (command) responsibilities of an application into distinct models, often even across different services or databases.

This article explains CQRS in detail, especially in the context of microservices, and provides implementation guidance using Spring Boot.

What is CQRS?

Definition

CQRS stands for −

  • Command − Operations that modify state (Create, Update, Delete).

  • Query − Operations that retrieve data (Read).

In CQRS, commands and queries are handled by separate models. This improves scalability, clarity, and performance—especially for applications with complex domain logic or high read/write loads.

Sr.No. Feature Traditional CRUD CQRS
1 Model Single model for both read and write Separate models
2 Data store One database Can use separate databases
3 Performance Limited optimization Queries and commands optimized independently
4 Complexity Simple More complex architecture
5 Scaling Hard to scale reads and writes separately Easy to scale separately

Why Use CQRS in Microservices?

Microservices often need to support −

  • High-volume reads (analytics, dashboards)

  • Complex writes (business logic, transactions)

  • Separate service responsibilities

CQRS allows microservices to −

  • Decouple the read model from the domain model

  • Use denormalized views for fast querying

  • Improve performance and scalability

  • Simplify event-driven communication

CQRS Architecture Overview

Here's a typical CQRS architecture in a microservice −

CQRS Architecture
  • Commands go through a command handler to update the write database.

  • Queries are executed against a read-optimized store (e.g., denormalized or cache).

Implementation Example in Spring Boot

Let's create a simple Product Service using CQRS−

Use Case

  • POST /products – Create a product

  • GET /products/{id} – Get product details

Maven Dependencies

<dependencies>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
   </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>

Domain Model

@Entity
public class Product {
   @Id
   private UUID id;
   private String name;
   private double price;

   // Getters and Setters
}

Command: Create Product

DTO

public class CreateProductCommand {
  private String name;
  private double price;
  // Getters and Setters
}

Product Repository

public interface ProductRepository extends JpaRepository<Product, UUID>{
}

Command Handler

@Service
public class ProductCommandHandler {

   @Autowired
   private ProductRepository productRepository;

   public UUID handle(CreateProductCommand command) {
      Product product = new Product();
      product.setId(UUID.randomUUID());
      product.setName(command.getName());
      product.setPrice(command.getPrice());
      productRepository.save(product);
      return product.getId();
   }
}

Command Controller

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

   @Autowired
   private ProductCommandHandler handler;

   @PostMapping
   public ResponseEntity createProduct(@RequestBody CreateProductCommand cmd) {
      UUID id = handler.handle(cmd);
      return ResponseEntity.ok("Product created with ID: " + id);
   }
}

Query: Read Product

DTO

public class ProductView {
   private UUID id;
   private String name;
   private double price;
}

Query Handler

@Service
public class ProductQueryHandler {

   @Autowired
   private ProductRepository productRepository;

   public ProductView getById(UUID id) {
      Product product = productRepository.findById(id).orElseThrow();
      ProductView view = new ProductView();
      view.setId(product.getId());
      view.setName(product.getName());
      view.setPrice(product.getPrice());
      return view;
   }
}

Query Controller

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

   @Autowired
   private ProductQueryHandler handler;

   @GetMapping("/{id}")
   public ResponseEntity getProduct(@PathVariable UUID id) {
      return ResponseEntity.ok(handler.getById(id));
   }
}

Event-Driven CQRS with Kafka or RabbitMQ

In advanced scenarios −

  • Write service publishes events (e.g., ProductCreatedEvent)

  • Read service listens and updates a read store (denormalized view)

Benefits of CQRS

Sr.No. Benefit Description
1 Performance Optimization Read and write stores optimized separately
2 Scalability Independent scaling of read and write paths
3 Separation of Concerns Cleaner code and responsibilities
4 Denormalized Read Model Faster reads via projections
5 Supports Event Sourcing Easily integrates with event-driven design

When to Use CQRS

Sr.No. Use When... Avoid When...
1 You have high read/write load imbalance Your app is simple with CRUD operations
2 You need to scale reads independently There's no performance bottleneck
3 You use event-driven architecture You need strong consistency everywhere
4 You require audit/event trail Your domain logic is very basic

Real-World Examples

Sr.No. Company Usage of CQRS
1 Uber Separate command/log and query/search systems
2 LinkedIn News feed write model vs read-optimized cache

Summary

Sr.No. Aspect Details
1 Pattern CQRS (Command Query Responsibility Segregation)
2 Use Decouple read and write responsibilities
3 Implementation Handlers, separate models, optional events
4 Tools Spring Boot, Spring Web, Spring Data JPA
5 Advanced Kafka, Event Sourcing, NoSQL for reads

Conclusion

CQRS is a powerful architectural pattern for building scalable, maintainable, and efficient microservices. It enables better separation of concerns, supports modern patterns like event sourcing, and provides performance benefits in high-scale systems.

Advertisements