CQRS in Java

What is CQRS?

CQRS is a pattern in software architecture that separates:

  • Commands → operations that change state (Create, Update, Delete)
  • Queries → operations that read state (Get, List, Search)

The main idea: read and write operations are handled separately, allowing each to be optimized independently.

Benefits:

  • Scalability (reads/writes can scale separately)
  • Clear separation of concerns
  • Easier to handle complex domain logic

CQRS Example in Java

Suppose we have a simple application to manage products.

1. Domain Model

public class Product {
    private String id;
    private String name;
    private double price;

    public Product(String id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    // Getters and Setters
}

2. Command Side (Write Operations)

import java.util.HashMap;
import java.util.Map;

public class ProductCommandService {
    private Map<String, Product> productStore = new HashMap<>();

    public void createProduct(String id, String name, double price) {
        Product product = new Product(id, name, price);
        productStore.put(id, product);
        System.out.println("Product created: " + name);
    }

    public void updateProductPrice(String id, double newPrice) {
        Product product = productStore.get(id);
        if (product != null) {
            product.setPrice(newPrice);
            System.out.println("Product price updated: " + product.getName());
        } else {
            System.out.println("Product not found!");
        }
    }

    // For demo purposes, exposing the store
    public Map<String, Product> getProductStore() {
        return productStore;
    }
}

3. Query Side (Read Operations)

import java.util.Map;

public class ProductQueryService {
    private Map<String, Product> productStore;

    public ProductQueryService(Map<String, Product> productStore) {
        this.productStore = productStore;
    }

    public Product getProductById(String id) {
        return productStore.get(id);
    }

    public void listAllProducts() {
        productStore.values().forEach(p -> 
            System.out.println("Product: " + p.getName() + ", Price: " + p.getPrice())
        );
    }
}

4. Using the Services

public class CQRSExample {
    public static void main(String[] args) {
        ProductCommandService commandService = new ProductCommandService();
        
        // Write operations
        commandService.createProduct("1", "Laptop", 1200);
        commandService.createProduct("2", "Phone", 800);
        commandService.updateProductPrice("2", 750);

        // Read operations
        ProductQueryService queryService = new ProductQueryService(commandService.getProductStore());
        queryService.listAllProducts();
        
        Product product = queryService.getProductById("1");
        System.out.println("Fetched product: " + product.getName() + ", Price: " + product.getPrice());
    }
}

How it works

  1. CommandService handles all writes (create/update/delete).
  2. QueryService handles all reads.
  3. They can use the same store (in-memory map for simplicity), but in real apps, the read model can be optimized for queries (like using a separate database or denormalized view).

Leave a Reply