Command/Query Responsibility Segregation (CQRS) is an architectural pattern that separates read operations (queries) from write operations (commands). This separation can improve scalability, maintainability, and performance, particularly in complex systems with high transaction volumes.
Ignoring CQRS in systems that require clear separation of commands and queries can lead to convoluted services, hard-to-maintain code, and performance bottlenecks.
Command and Query separation using MediatR:
// Command
public class CreateUserCommand : IRequest<Guid>
{
public string Name { get; set; }
}
// Command Handler
public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, Guid>
{
private readonly AppDbContext _context;
public CreateUserCommandHandler(AppDbContext context) { _context = context; }
public async Task<Guid> Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
var user = new User { Name = request.Name };
_context.Users.Add(user);
await _context.SaveChangesAsync(cancellationToken);
return user.Id;
}
}
// Query
public class GetUserByIdQuery : IRequest<User>
{
public Guid Id { get; set; }
}
// Query Handler
public class GetUserByIdQueryHandler : IRequestHandler<GetUserByIdQuery, User>
{
private readonly AppDbContext _context;
public GetUserByIdQueryHandler(AppDbContext context) { _context = context; }
public async Task<User> Handle(GetUserByIdQuery request, CancellationToken cancellationToken)
{
return await _context.Users.FindAsync(request.Id);
}
}CQRS example using Spring services:
// Command Service
@Service
public class CreateUserService {
@Autowired
private UserRepository repository;
public User execute(String name) {
User user = new User(name);
return repository.save(user);
}
}
// Query Service
@Service
public class GetUserService {
@Autowired
private UserRepository repository;
public Optional<User> execute(Long id) {
return repository.findById(id);
}
}CQRS example using separate controllers:
// Command: create user
app.post('/users', (req, res) => {
const user = { id: uuid(), name: req.body.name };
db.users.push(user);
res.status(201).json(user);
});
// Query: get user by ID
app.get('/users/:id', (req, res) => {
const user = db.users.find(u => u.id === req.params.id);
res.json(user);
});CQRS example in API routes:
// pages/api/users/index.ts (Command)
export default function handler(req, res) {
if(req.method === 'POST'){
const user = { id: crypto.randomUUID(), name: req.body.name };
users.push(user);
res.status(201).json(user);
}
}
// pages/api/users/[id].ts (Query)
export default function handler(req, res) {
const user = users.find(u => u.id === req.query.id);
res.status(200).json(user);
}CQRS with separate endpoints:
from flask import Flask, request, jsonify
app = Flask(__name__)
users = []
# Command
@app.route('/users', methods=['POST'])
def create_user():
user = {'id': len(users)+1, 'name': request.json['name']}
users.append(user)
return jsonify(user), 201
# Query
@app.route('/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
user = next((u for u in users if u['id'] == user_id), None)
return jsonify(user)CQRS with separate command and query services:
// app/Services/Commands/CreateUser.php
namespace App\Services\Commands;
use App\Models\User;
class CreateUser {
public function execute($name) {
return User::create(['name' => $name]);
}
}
// app/Services/Queries/GetUserById.php
namespace App\Services\Queries;
use App\Models\User;
class GetUserById {
public function execute($id) {
return User::find($id);
}
}CQRS is a powerful architectural pattern that separates commands and queries to improve system clarity, maintainability, and performance. By separating read and write responsibilities, developers can optimize each path independently and enforce stricter domain rules.
Key Takeaways: