CQRS Design Pattern and Spring Boot Microservices Implementation Guide


The image showcases an architectural pattern for Command Query Responsibility Segregation (CQRS). It demonstrates the segregation of the write operations (commands) and read operations (queries) into separate services and components for enhanced scalability, maintainability, and performance in microservices.

Here are the key components illustrated in the diagram:

  1. Microservices:

    • Order Microservice: Handles commands and queries related to orders.
    • Customer Microservice: Manages commands and queries related to customers.
  2. Command and Query Segregation:

    • Each microservice has:
      • A Command Endpoint for write operations (e.g., creating, updating data).
      • A Query Endpoint for read operations.
      • Separate Command Service and Query Service with their respective models (Command Model and Query Model).
  3. Event-Driven Architecture:

    • Event Publishers and Event Consumers facilitate communication between services using an Event Store.
    • Events are relayed through a Messaging System for asynchronous processing and eventual consistency.
  4. Read Storage:

    • Dedicated databases or storage for query models ensure optimized performance for read operations.

Below is an example of implementing CQRS (Command Query Responsibility Segregation) in a Spring Boot microservices architecture, focusing on Order Management.

Architecture Overview

  • Order Command Service:
    • Handles create, update, and delete operations for orders.
    • Publishes events to a message broker (e.g., Kafka or RabbitMQ).
  • Order Query Service:
    • Handles read operations for orders.
    • Maintains a denormalized read database for optimized query performance.

Technologies

  • Spring Boot: Framework for building microservices.
  • Spring Data JPA: For persistence in the command side.
  • MongoDB: For denormalized read data.
  • Kafka: For event-driven communication.
  • PostgreSQL: For write data.

Implementation Steps

1. Order Command Service

Dependencies in pom.xml:

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

Entity Example:

@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String product;
    private Integer quantity;
    private String status; // e.g., CREATED, UPDATED, CANCELLED
    // Getters and setters
}

Repository:

public interface OrderRepository extends JpaRepository<Order, Long> {}

Service:

@Service
public class OrderCommandService {

    private final OrderRepository orderRepository;
    private final KafkaTemplate<String, String> kafkaTemplate;

    public OrderCommandService(OrderRepository orderRepository, KafkaTemplate<String, String> kafkaTemplate) {
        this.orderRepository = orderRepository;
        this.kafkaTemplate = kafkaTemplate;
    }

    public Order createOrder(Order order) {
        Order savedOrder = orderRepository.save(order);
        kafkaTemplate.send("order-events", "OrderCreated: " + savedOrder.getId());
        return savedOrder;
    }

    public Order updateOrder(Long id, Order updatedOrder) {
        Order order = orderRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("Order not found"));
        order.setProduct(updatedOrder.getProduct());
        order.setQuantity(updatedOrder.getQuantity());
        Order savedOrder = orderRepository.save(order);
        kafkaTemplate.send("order-events", "OrderUpdated: " + savedOrder.getId());
        return savedOrder;
    }
}

Controller:

@RestController
@RequestMapping("/api/orders")
public class OrderCommandController {

    private final OrderCommandService commandService;

    public OrderCommandController(OrderCommandService commandService) {
        this.commandService = commandService;
    }

    @PostMapping
    public ResponseEntity<Order> createOrder(@RequestBody Order order) {
        return ResponseEntity.ok(commandService.createOrder(order));
    }

    @PutMapping("/{id}")
    public ResponseEntity<Order> updateOrder(@PathVariable Long id, @RequestBody Order order) {
        return ResponseEntity.ok(commandService.updateOrder(id, order));
    }
}

2. Order Query Service

Dependencies in pom.xml:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-mongodb</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.kafka</groupId>
        <artifactId>spring-kafka</artifactId>
    </dependency>
</dependencies>

MongoDB Model:

@Document(collection = "orders")
public class OrderView {
    @Id
    private String id;
    private String product;
    private Integer quantity;
    private String status;
    // Getters and setters
}

Repository:

public interface OrderViewRepository extends MongoRepository<OrderView, String> {}

Event Consumer:

@Component
public class OrderEventConsumer {

    private final OrderViewRepository orderViewRepository;

    public OrderEventConsumer(OrderViewRepository orderViewRepository) {
        this.orderViewRepository = orderViewRepository;
    }

    @KafkaListener(topics = "order-events", groupId = "order-group")
    public void consume(String message) {
        String[] event = message.split(": ");
        String eventType = event[0];
        String orderId = event[1];

        switch (eventType) {
            case "OrderCreated":
                // Fetch order details from the command database if needed
                OrderView newOrder = new OrderView();
                newOrder.setId(orderId);
                // Set other fields...
                orderViewRepository.save(newOrder);
                break;
            case "OrderUpdated":
                OrderView existingOrder = orderViewRepository.findById(orderId)
                        .orElseThrow(() -> new RuntimeException("Order not found"));
                // Update fields...
                orderViewRepository.save(existingOrder);
                break;
        }
    }
}

Query Service:

@Service
public class OrderQueryService {

    private final OrderViewRepository orderViewRepository;

    public OrderQueryService(OrderViewRepository orderViewRepository) {
        this.orderViewRepository = orderViewRepository;
    }

    public List<OrderView> getAllOrders() {
        return orderViewRepository.findAll();
    }

    public OrderView getOrderById(String id) {
        return orderViewRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("Order not found"));
    }
}

Controller:

@RestController
@RequestMapping("/api/orders")
public class OrderQueryController {

    private final OrderQueryService queryService;

    public OrderQueryController(OrderQueryService queryService) {
        this.queryService = queryService;
    }

    @GetMapping
    public List<OrderView> getAllOrders() {
        return queryService.getAllOrders();
    }

    @GetMapping("/{id}")
    public OrderView getOrderById(@PathVariable String id) {
        return queryService.getOrderById(id);
    }
}

Summary of Key Features

  1. Command Side:

    • Orders are persisted to a PostgreSQL database.
    • Events are published via Kafka.
  2. Query Side:

    • Orders are denormalized and stored in MongoDB for optimized read performance.
    • Kafka listeners update the read database in real-time.
  3. Messaging System:

    • Kafka ensures asynchronous communication and eventual consistency.

Buy Now – Unlock Your Microservices Mastery for Only $9!

Get your copy now for just $9! and start building resilient and scalable microservices with the help of Microservices with Spring Boot 3 and Spring Cloud.

Popular posts from this blog

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Java Stream API - How to convert List of objects to another List of objects using Java streams?

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Java, Spring Boot Mini Project - Library Management System - Download

ReactJS, Spring Boot JWT Authentication Example

Top 5 Java ORM tools - 2024

Java - Blowfish Encryption and decryption Example

Spring boot video streaming example-HTML5

Google Cloud Storage + Spring Boot - File Upload, Download, and Delete