Command Query Separation (CQS) states that every method should either be a command that performs an action or a query that returns data, but not both.
Core Concept
- Queries: Return data and have no side effects
- Commands: Perform actions and modify state, but return nothing (or only success/failure)
Benefits
- Predictability: Queries can be called safely multiple times
- Testability: Commands and queries can be tested separately
- Reasoning: Easier to understand method purposes
- Caching: Queries can be safely cached
Example
Violates CQS
// BAD: Both queries and modifies state
public class Stack
{
public int Pop()
{
// Gets value (query) AND removes it (command)
int value = _items[_top];
_items[_top] = default;
_top--;
return value;
}
}Follows CQS
// GOOD: Separate query and command
public class Stack
{
// Query: Returns data, no side effects
public int Peek()
{
return _items[_top];
}
// Command: Modifies state, returns nothing
public void Pop()
{
_items[_top] = default;
_top--;
}
}
// Usage
int value = stack.Peek(); // Query
stack.Pop(); // CommandPractical Challenges
Server-Generated IDs
When creating entities, you often need the generated ID:
// Pragmatic violation of CQS
public class UserRepository
{
// Returns ID (query) after creating (command)
public Guid Create(User user)
{
_db.Users.Add(user);
_db.SaveChanges();
return user.Id; // Need this for further operations
}
}This is a known pragmatic exception - sometimes you need confirmation of what was created.
Alternative: Use Return Values for Success/Failure
// Command can return success indicator
public bool TryPop(out int value)
{
if (_top < 0)
{
value = default;
return false;
}
value = _items[_top];
_items[_top] = default;
_top--;
return true;
}CQRS Extension
Command Query Responsibility Segregation (CQRS) extends CQS to the architectural level:
- Separate read models from write models
- Different data structures for queries vs commands
- Often used with event sourcing
See read models and boundaries for CQS taken to the architectural level: a single write door, and inert value objects (not live records) crossing boundaries.
Resources
Related Concepts
- Design Patterns - Functional Core, Imperative Shell
- Domain Driven Design - CQRS pattern
- Design Principles
- Good Software Practices