Reading Time: 13 minutes

Combining MuleSoft with CQRS architecture can unlock new levels of efficiency and scalability in enterprise integration. When designing APIs, people often focus solely on the basic request and response cycle without considering design patterns or the challenges of distributed contexts, such as gaming or performance-critical applications.

These contexts require more planning and sophisticated architecture to meet low latency needs. MuleSoft’s out-of-the-box capabilities allow for the implementation of complex and contemporary design patterns, including distributed runtimes and event stores, which deliver exceptionally fast performance.

By breaking the traditional iPaaS paradigm, MuleSoft can be applied to more challenging environments like gaming, where even a one second delay will feel slow. We are among the few capable of meeting these challenges, broadening the typical use case of MuleSoft in a business context. At scale, this not only enhances performance but also optimizes costs, making it an ideal solution for enterprises looking to achieve high efficiency and scalability in their integration processes.

Understanding CQRS architecture

Command Query Responsibility Segregation (CQRS) is a design pattern that separates an application’s responsibilities into commands and queries. This separation enhances scalability, performance, and maintainability by handling requests differently based on their nature.

Commands

Commands deal with requests that change data, like creating, updating, or deleting. These operations usually involve complex business logic and validation, and they return a status indicating success or failure.

Queries

Queries handle requests that retrieve data, such as reading or searching. These operations are typically simple, fast, and often cacheable, returning the requested data.

Benefits of CQRS

  • Improved scalability: Commands and queries can be scaled independently, making resource use more efficient and handling high request volumes easier.
  • Reduced complexity: Separating commands and queries simplifies the development and maintenance of complex business logic.
  • Enhanced flexibility: Different data models, databases, or technologies can be used for commands and queries, allowing for better adaptation to changing requirements.
  • Improved performance: Optimizing commands and queries for their specific tasks leads to faster processing and response times. Techniques like caching can further enhance performance.

By adopting CQRS architecture, you can create more efficient, scalable, and maintainable systems. Adopting CQRS with MuleSoft can provide your organization a performance boost that will delight your customers.

CQRS example use case: Gaming scoreboard

Here’s an example of an online gaming application, where players join games, play them and scores are recorded. You can use the CQRS architecture to decouple your services and reduce latency while leveraging MuleSoft to build swiftly and reuse components. The architecture is like microservices with an eventually consistent, semi-permanent cache. 

Architecture Diagram for Mulesoft CQRS
Architecture Diagram for MuleSoft CQRS

Notice we have:

  • 3x Mulesoft Apps
    • Server / app layer: This app is your entry point and directs requests to the Command and Read Apps as required
    • Command app: This app controls inserts, updates and deletes (POST/PATCH/PUT/DELETE requests)
    • Read app: This app controls reads (GET requests), updating projections (views) and the read database (MuleSoft Object Store v2) lives inside here
  • 2x Databases
    • Postgres is used for your write database. Make sure it’s write optimized with few indexes, fast disks and batch writing
    • Mule Object Store is an in memory key value data store that runs inside the Mule Read App runtime
  • 1x Eventbus
    • Anypoint MQ is your queue of choice. It’s fast, easy to implement, and is the recommended method for MuleSoft app-to-app communication

There are three possible Paths we can take:

  1. Command path
  2. Read path – read database contains data we want
  3. Read path – read database is missing the data

Command Path

  • [Mule Server App] 1. Client performs a Create / Update / Delete operation
  • [Mule Command App] 2. Command App writes to the write db (Postgres)
  • [Mule Command App] 3. Upon write success, pushes success message to eventbus (Anypoint MQ)
  • [Mule Read App] 4. Message is read from the queue (Anypoint MQ) and the Projection (view) is updated
  • [Mule Read App] 5. Write into read db (Object Store v2)

Read Path – Data exists

For fastest reads with eventual consistency:

  • [Mule Server App] 1. Client performs a Read / Search operation
  • [Mule Read App] 2. Read model reads from read db (Object Store v2) and returns data

This data might be stale, as we implement eventual consistency between the write and read databases. For instance if we make a read and update for the same data at the same time, the data returned from the read will be the existing data, not the data from the update operation.

Read Path – Data missing

In the case where we read data, but it’s not yet in the read database, we fallback to the write database (Postgres) to get the data.

Highlighted CQRS diagram for data missing in read database
Highlighted CQRS diagram for data missing in read database
  • [Mule Server App] 1. Client calls a read
  • [Mule Read App] 2. Read model tries to read from the read db (Object Store v2)
  • [Mule Read App] 3. Data isn’t there, so you read from the write db (Postgres)
  • [Mule Read App] 4. Return the data and update the read db with the new projection (view)

This scenario might happen if you’ve cleared this particular data in the read database, but it exists in the write database. That’s the semi-permanent cache part of CQRS, where in our scenario, using Mule Object Store v2, Time To Live (TTL) might expire. Object Store v2, has a rolling TTL by default. Where “Accessing the data during the last seven days of a 30-day window extends the TTL for another 30 days”. Making it possible that “hot” data, that’s accessed often, is permanently cached. Depending on the read db you choose, you may have different constraints.

Benchmarks – Up to 2.9x faster response times

After implementing CQRS, our app was 2.9x on average. At the 95th and 99th percentiles we still see improvements, but less so.

Benchmarks chart
Benchmarks chart

Experiment Setup

  • Mule CQRS and Monolith are using CloudHub 2.0 (0.1 vCore, 1GB memory) in AWS Sydney Region
    • Mule Object Store v2 in Sydney Region
    • One set of Mule Apps is CQRS
      • Running 4x endpoints
    • One App is the monolith. It just reads and writes to Postgres directly
      • Running 4x endpoints
  • My home machine is a 2020 MacBook Pro (Intel) in Sydney Australia
  • Test software: Locust
    • Settings: 
    • Round robin hit all 4x endpoints
    • 10 concurrent users
    • Ramp up 1 user/second spawn rate
    • 3 minute run time
  • Postgres Database is Neon.tech (free tier) in AWS Sydney Region

See Appendix 1 for the Raw Results table.

Conclusion

By adopting the CQRS architecture with MuleSoft, you are poised to build more efficient, scalable, and maintainable systems. The separation of commands and queries not only enhances performance and scalability but also simplifies the complexity of your business logic. With MuleSoft’s robust integration capabilities, you can streamline your processes, improve response times, and ultimately provide a superior experience for your users.

Whether you are managing high-volume transactions or ensuring quick data retrieval, CQRS with MuleSoft offers a versatile solution that adapts to your growing needs. Implementing this approach can significantly boost your application’s performance, as evidenced by our benchmarking results, leading to happier customers. Now is the time to leverage the power of CQRS and MuleSoft to drive your business forward.

Appendix 1 – Raw Results

Scenario Without CQRS – Read directly from Postgres every time

Endpoint NameRequest CountMedian Response TimeAverage Response TimeMin Response TimeMax Response TimeReqs/sec95%99%99.9%
/read-game-start-delay-mean4008185.956.0537.62.2110230540
/read-game-start-delays4288286.960.8480.22.4110170480
/read-highest-point-scorers3998288.257.9352.02.2120329350
/read-most-common-opponents3868285.157.8297.92.1110210300
Aggregated16138286.556.0537.68.9110210440