loading...

. . . . . .

Let’s make something together

Give us a call or drop by anytime, we endeavour to answer all enquiries within 24 hours on business days.

Find us

504, Gala Empire,
Driver-in Road, Thaltej,
Ahmedabad – 380054.

Email us

For Career – career@equalefforts.com
For Sales – sales@equalefforts.com
For More Info – info@equalefforts.com

Phone support

Phone: +91 6357 251 116

Understanding the Basics of CQRS Design Patterns

  • By Jitin Jadavra
  • April 11, 2024
  • 28 Views

What is the CQRS Pattern?

CQRS (Command Query Responsibility Segregation) is an architectural pattern that emphasizes the separation of commands (methods that change state) from queries (methods that read state). It is based on the Command Query Separation (CQS) principle, which was first introduced by Bertrand Meyer. CQRS recommends that we classify the operations performed on domain objects into two distinct categories – Queries and Commands, to help us build a better and more scalable architecture.

Queries return a result and do not change the observable state of a system. Commands change the state of the system but do not necessarily return a value.

It is a design pattern that suggests separating read and write operations into separate models to improve the scalability, performance, and maintainability of the system.

Challenges without CQRS

Normally, in monolithic applications, most of the time we have one database, and this database should respond to both query and update operations. That means a database is both working for complex join queries and also performing CRUD operations. But if the application becomes more complex this query and crud operations will also be going to be an unmanageable situation.

For example, when reading a database, if your application requires some query that needs to join more than 10 tables, this will lock the database due to the latency of query computation. Also if we give an example of writing a database when performing CRUD operations we would need to make complex validations and process long business logic, so this will cause to lock database operation.

Overcomes the Challenges

In this case, the Command side would handle the creation, updating, and deletion of products. This would involve services that allow users to add products to the system, update product details, and delete products from the system.

On the other hand, the Query side would be responsible for handling user requests for product information. This would involve services that allow users to search for products based on various criteria, such as name, category, price range, etc. The Query side would be optimized for fast-read operations, and the data would be stored in a denormalized form to improve query performance.

To implement this using CQRS in a Spring Boot microservices architecture, we could have separate microservices for the Command and Query sides. The command microservice would handle product creation, updates, and deletions, and it would publish events to a Kafka topic whenever a product is created, updated, or deleted.

The Query microservice would subscribe to this Kafka topic and update its data that is stored with the latest product information. It would expose a set of REST endpoints that allow users to search for products based on various criteria. The Query microservice could use Elasticsearch or another search engine to provide fast search capabilities.

When to Use CQRS Pattern?

  • The CQRS pattern is particularly useful in situations where the read and write operations have different performance requirements. For example, if you have an application that requires frequent read operations but infrequent write operations, the CQRS pattern can help optimize the performance of the read operations.
  • The CQRS pattern is also useful in situations where the read-and-write models have different data structures. For example, if the read model requires a different data structure than the write model, the CQRS pattern can help to simplify the implementation by separating the concerns of reading and writing data.
  • It’s worth noting that the CQRS pattern is not a one-size-fits-all solution, and it may not be appropriate for all applications. Before implementing the CQRS pattern, you should carefully consider your applications’ specific requirements and whether the CQRS pattern is the right fit.
  • Scenarios where the two teams can specialize in the complex domain model and the read model and user interfaces respectively.
  • Scenarios where the system may evolve and contain multiple model versions, or where business rules change frequently.
  • Integration with other systems should not affect availability in case of temporal failure, especially in combination with event sourcing.

How to Sync Databases with CQRS?

When we separate read and write databases into 2 different databases, the main consideration is to sync these two databases properly. However, this solution creates a consistency issue because the data would not be reflected immediately since we have implemented async communication with message brokers. This will operate the principle of “eventual consistency”. The read database eventually synchronizes with the write database, and it can take some time to update the read database in the async process.

Messaging queues would be useful in this situation to keep the read models updated when and if the write model changes. If possible, it’s best to use the built-in storage mechanism, such as subscriptions for event stores or change detection capture for relational databases, as it will make your solution more straightforward.

Benefits of CQRS

  • Improved Scalability: By separating the responsibilities of reading and writing data, you can optimize the read and write operations independently, resulting in a more scalable application.
  • Improved Performance: By optimizing the read and write operations separately, you can improve the performance of your application.
  • Improved Maintainability: The CQRS pattern helps to improve the maintainability of the code by separating the concerns of reading and writing data.
  • Enhanced Security: The CQRS pattern can also help to improve security by separating the read and write models. This allows you to apply different security measures to the read and write operations, such as limiting access to the write model to a select group of users

Drawbacks of CQRS

  • Only a complex domain model can benefit from the added complexity of this pattern, a simple domain model can be managed without all this.
  • Naturally, it leads to code duplication to some extent, which is an acceptable evil compared to the gain it leads us to; however, individual judgment is advised
  • Separate repositories lead to problems of consistency, and it’s difficult to keep the write and read repositories in perfect sync always; we often have to settle for eventual consistency

Introducing the CQRS pattern in our application

We’ll begin by describing a simple application in Java that builds a domain model.

Implementing the Command Side of the Application

On the Command side, we can have a Spring Boot microservice that handles the creation, updating, and cancellation of orders. We can use Spring Data JPA to interact with the database and Apache Kafka to publish events when orders are created, updated, or cancelled. Here’s an example of how the service might look like:

@Service
public class OrderService {
  
   @Autowired
   private KafkaTemplate<String, OrderEvent> kafkaTemplate;
  
   @Autowired
   private OrderRepository orderRepository;
  
   public void createOrder(Order order) {
       // Validate the order
      
       // Save the order to the database
       orderRepository.save(order);
      
       // Publish an event to Kafka
       OrderEvent event = new OrderEvent(order.getId(), OrderEventType.CREATED);
       kafkaTemplate.send("orders", event);
   }
  
   public void updateOrder(Order order) {
       // Validate the order
      
       // Save the order to the database
       orderRepository.save(order);
      
       // Publish an event to Kafka
       OrderEvent event = new OrderEvent(order.getId(), OrderEventType.UPDATED);
       kafkaTemplate.send("orders", event);
   }
  
   public void cancelOrder(Long orderId) {
       // Get the order from the database
       Order order = orderRepository.findById(orderId)
           .orElseThrow(() -> new OrderNotFoundException(orderId));
      
       // Cancel the order
       order.setStatus(OrderStatus.CANCELLED);
      
       // Save the order to the database
       orderRepository.save(order);
      
       // Publish an event to Kafka
       OrderEvent event = new OrderEvent(order.getId(), OrderEventType.CANCELLED);
       kafkaTemplate.send("orders", event);
   }
}

Implementing the Query Side of the Application

@Service
public class OrderQueryService {
  
   @Autowired
   private KafkaTemplate<String, OrderEvent> kafkaTemplate;
  
   @Autowired
   private OrderRepository orderRepository;
  
   @PostConstruct
   public void init() {
       // Subscribe to the orders topic
       kafkaTemplate.subscribe("orders", this::handleOrderEvent);
   }
  
   private void handleOrderEvent(ConsumerRecord<String, OrderEvent> record) {
       OrderEvent event = record.value();
      
       switch (event.getEventType()) {
           case CREATED:
           case UPDATED:
               // Update the order in the data store
               Order order = orderRepository.findById(event.getOrderId())
                   .orElseThrow(() -> new OrderNotFoundException(event.getOrderId()));
               order.setStatus(event.getEventType() == OrderEventType.CREATED ?
                   OrderStatus.CREATED : OrderStatus.UPDATED);
               orderRepository.save(order);
               break;
           case CANCELLED:
               // Delete the order from the data store
               orderRepository.deleteById(event.getOrderId());
               break;
           default:
               throw new IllegalArgumentException("Invalid order event type: " + event.getEventType());
       }
   }
  
   public List<Order> searchOrders(OrderSearchCriteria criteria) {
       // Use Spring Data JPA to search for orders in the data store
       // Return a list of orders that match the search criteria
   }
}

Separating the Command and Query sides allows us to optimize each side for its specific use case. The Command side can concentrate on ensuring data consistency and enforcing business rules, while the Query side can concentrate on providing fast and responsive queries to clients.

Conclusion

CQRS is a pattern that improves scalability, maintainability, and flexibility. By understanding its pros and cons, and with the help of a Spring Boot example, you can explore how CQRS can benefit your projects.

Leave a Reply

Your email address will not be published. Required fields are marked *