CQRS – keep read & write responsibility separate

CQRS (Command-Query Responsibility Segregation) is a design pattern that separates the responsibility of handling commands (write operations) from queries (read operations) in a system. This pattern can improve the scalability, performance, and maintainability of the system by optimising each side independently.

The basic idea behind CQRS is to split a system into two parts: a command side that handles all write operations, and a query side that handles all read operations. This allows each side to be optimized independently, and to use different technologies, data stores, and architectural patterns that are best suited for their specific tasks.

On the command side, CQRS typically involves the use of domain-driven design (DDD) principles and event sourcing to model the business logic and maintain a log of all changes to the system. Commands are handled by aggregates, which are responsible for enforcing business rules and updating the state of the system. The state changes are then recorded as events, which can be stored in an event store or a message broker.

On the query side, CQRS typically involves the use of a separate read model that is optimized for querying and reporting. This read model is populated by the events generated by the command side, and can be stored in a relational database, a NoSQL database, or a search engine, depending on the specific requirements of the system.

Here’s an example of how CQRS might be implemented in a simple e-commerce system:

// Command side

public class Order {
  private UUID orderId;
  private UUID customerId;
  private List<OrderLineItem> lineItems;
  
  // Constructor and getters/setters omitted for brevity
  
  public void addItem(Product product, int quantity) {
    // Add a new line item to the order
  }
  
  public void removeItem(Product product) {
    // Remove a line item from the order
  }
  
  public void place() {
    // Place the order and generate an event
  }
}

public class OrderAggregate {
  private UUID orderId;
  private OrderState state;
  private List<OrderEvent> events;
  
  // Constructor and methods omitted for brevity
  
  public void handleCommand(OrderCommand command) {
    // Apply the command to the aggregate and generate an event
  }
  
  public List<OrderEvent> getUncommittedEvents() {
    // Get the list of uncommitted events
  }
  
  public void markEventsAsCommitted() {
    // Mark the events as committed
  }
}

// Query side

public class OrderDto {
  private UUID orderId;
  private UUID customerId;
  private List<OrderLineItemDto> lineItems;
  
  // Constructor and getters/setters omitted for brevity
}

public interface OrderRepository {
  public void save(OrderDto orderDto);
  public List<OrderDto> findByCustomerId(UUID customerId);
}

public class OrderEventHandler {
  public void handleEvent(OrderEvent event) {
    // Update the read model based on the event
  }
}

public class OrderQueryService {
  private OrderRepository orderRepository;
  
  // Constructor omitted for brevity
  
  public List<OrderDto> getOrdersByCustomerId(UUID customerId) {
    List<OrderDto> orders = orderRepository.findByCustomerId(customerId);
    return orders;
  }
}
Java

In this example, the Order class represents the business logic of an order, and the OrderAggregate class represents the command side of the CQRS pattern. The OrderAggregate class uses the OrderCommand and OrderEvent classes to handle commands and generate events, which are then stored in an event store or a message broker.

References: