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.
