What is CQS:
Command Query Separation (CQS) is a software design principle that states that methods should either be commands (which perform an action and do not return a value) or queries (which return a value and do not change the state of the system). CQS helps improve the readability, maintainability, and testability of code.
In React:
In React, CQS can be implemented by separating the logic that changes state from the logic that renders the UI. Here is an example of how this can be done:
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
function handleIncrement() {
setCount(count + 1);
}
function handleDecrement() {
setCount(count - 1);
}
return (
<div>
<h1>{count}</h1>
<button onClick={handleIncrement}>Increment</button>
<button onClick={handleDecrement}>Decrement</button>
</div>
);
}
export default Counter;
In this example, the handleIncrement
and handleDecrement
functions are commands because they change the state of the count
variable by calling the setCount
function. The return
statement is a query because it renders the current value of the count
variable to the UI.
Separating commands from queries makes it easier to test the logic that changes state because it can be tested separately from the UI rendering logic. It also makes it easier to reason about the code because the two types of logic are clearly separated.
In NodeJS:
Command Query Separation (CQS) is a software design principle that encourages developers to separate methods into two distinct categories: commands and queries. Commands are methods that modify state and don't return a value, while queries are methods that return a value and don't modify state. In Node.js, you can implement CQS to write more modular, testable, and maintainable code.
Here's an example of how you can implement CQS in Node.js using JavaScript:
class UserRepository {
constructor(database) {
this.database = database;
}
async createUser(user) {
// This is a command that saves the user to the database
await this.database.save(user);
}
async getUserById(id) {
// This is a query that returns the user with the specified ID
const user = await this.database.get(id);
return user;
}
}
module.exports = UserRepository;
In this example, UserRepository
is a class that represents a data access layer for managing user data. It takes a database object as a constructor parameter, which is used to perform database operations.
The createUser
method is a command that saves the user object to the database. It doesn't return anything, but it modifies the database state.
The getUserById
method is a query that returns the user object with the specified ID from the database. It doesn't modify the database state, but it returns a value.
By separating commands and queries, you can easily test them separately and reuse them in other parts of your application. It also makes it easier to reason about the code because the two types of methods have clearly defined responsibilities.
In addition, you can use CQS in other parts of your Node.js application, such as separating the handling of HTTP requests from the business logic. This can help you write more maintainable, modular, and testable code.
Reason and Benefit of CQRS
Reading the data is more frequent than writing. Generally, it is in the ratio of 10:1 or sometimes 100:1.
Reading operation should be fast. A user feels frustrated if a query takes more than half a second.
The user can tolerate the write operation slowness, as they know that some important action happens in the system.
Write operation changes the state of the system. World looks different before and after the writing.
Read operation wants to retrieve quite a bit of data while writing affects only one or two rows at a time.
Write operation changes the state. Thus, they have side effects.
Reading data
Since reading the data should be fast enough, we should make sure of the following.
The data should be accessed in a manner, which needs the least amount of DB queries possible for necessary context only.
Aggregated data should not be calculated on the fly. Rather it should pre-calculated.
No business logic should be executed while reading the data. It should execute while writing.
Read operations should not have any side effects because they do not make any changes.
Writing data
Writing operation should not return any result except the status message (success or failure).
Write should send a limited set of data, which is mostly one row at a time to write or update.