- Java Microservices Tutorial
- Java Microservices - Home
- Microservices - Introduction
- Microservices vs Monolith vs SOA
- Java Microservices - Environment Setup
- Java Microservices - Advantages of Spring Boot
- Java Microservices - Design Patterns
- Java Microservices - Domain Driven Design
- Java Microservices - Decomposition by Business Capability
- Java Microservices - Decomposition by Subdomain
- Java Microservices - Backend for Frontend
- Java Microservices - The Strangler Pattern
- Java Microservices - Synchronous Communication
- Java Microservices - Asynchronous Communication
- Java Microservices - Saga Pattern
- Java Microservices - Centralized Logging (ELK Stack)
- Java Microservices - Event Sourcing
- Java Microservices - CQRS Pattern
- Java Microservices - Sidecar Pattern
- Java Microservices - Service Mesh Pattern
- Java Microservices - Circuit Breaker Pattern
- Java Microservices - Distributed Tracing
- Java Microservices - Control Loop Pattern
- Java Microservices - Database Per Service
- Java Microservices - Bulkhead Pattern
- Java Microservices - Health Check API
- Java Microservices - Retry Pattern
- Java Microservices - Fallback Pattern
- Java Microservices Useful Resources
- Java Microservices Quick Guide
- Java Microservices Useful Resources
- Java Microservices Discussion
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 −
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 | 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.