Post

CQRS Pattern

CQRS Pattern

CQRS (Command Query Responsibility Segregation) is an architectural software design pattern where you separate write operations (commands) from read operations (queries). As application grow, it can become more difficult to optimize read and write operation on a single data, so with CQRS pattern it very useful to help fixed this problem.

πŸ’‘ Core concept

Instead of one model that handles everything:

  • Commands β†’ change state (write, update, delete)
  • Queries β†’ read state

The key idea is: the model used to write data is different from the model used to read data.

πŸ“Œ When to use

It is commonly use for:

  • Trading systems
  • Game servers
  • Real-time simulation engines
  • High-throughput backend services

Advantages

  • Separate optimization for read vs write
  • Easier scaling (read replicas, write DB separation)
  • Cleaner command logic (no mixing with queries)
  • Better fit for event driven systems

Disadvantages

  • More complexity
  • Data synchronization challenges between models
  • Often overkill for simple application

πŸ—ƒοΈ Structure design code

Let’s build a minimal in-memory example.

1. Domain model

1
2
3
4
struct Account {
    int id;
    double balance;
};

2. Command (writes side)

Commands represent behaviour (create, update, delete).

1
2
3
4
5
6
7
8
9
struct DepositCommand {
    int accountId;
    double amount;
};

struct WithdrawCommand {
    int accountId;
    double amount;
};

Command handler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class AccountCommandHandler {
private:
    std::unordered_map<int, Account> &store;

public:
    AccountCommandHandler(std::unordered_map<int, Account> &store)
        : store(store) {}
    
    void handle(const DepositCommand &cmd) {
        store[cmd.accountId].balance += cmd.amount;
    }

    void handle(const WithdrawCommand &cmd) {
        Account &account = store[cmd.accountId];

        if (account.balance >= cmd.amount)
            account.balance -= cmd.amount;
    }
};

3. Query (read side)

Queries don’t modify state.

1
2
3
struct GetBalanceQuery {
    int accountId;
};

Query handler:

1
2
3
4
5
6
7
8
9
10
11
12
class AccountQueryHandler {
private:
    std::unordered_map<int, Account> &store;

public:
    AccountQueryHandler(std::unordered_map<int, Account> &store)
        : store(store) {}

    double handle(const GetBalanceQuery &query) const {
        return store.at(query.accountId).balance;
    }
};

4. Usage example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <unordered_map>

int main() {
    std::unordered_map<int, Account> store;

    store[1] = {1, 100.0};

    AccountCommandHandler cmd(store);
    AccountQueryHandler query(store);

    cmd.handle(DepositCommand{1, 50.0});
    cmd.handle(WithdrawCommand{1, 30.0});

    double balance = query.handle(GetBalanceQuery{1});

    std::cout << "Account balance: " << balance << std::endl;

    return 0;
}

🏁 Conclusion

CQRS Pattern is most effective in system where read and write operation have difference performance, scalability, or complexity requirements but in real systems, CQRS is often combined with:

  • Event Sourcing Pattern β†’ store events instead of state.
  • Message broker (Kafka, ZeroMQ, RabbitMQ) β†’ enable asynchronous communication.
  • Cache Databases (Redis, Elasticsearch) β†’ optimize query performance.
This post is licensed under CC BY 4.0 by the author.