Skip to main content

API and Interface Design Principles

Principles for designing clean interfaces from martinfowler.com

Command Query Separation (CQS)

Definition: Divide an object's methods into two distinct categories: queries and commands.

The Two Categories

Queries

  • Return a result
  • Do NOT change observable state of the system
  • Free of side effects
  • Can be called multiple times safely
  • Order of execution doesn't matter

Commands (Modifiers)

  • Change the state of the system
  • Do NOT return a value
  • Have side effects
  • Order and frequency matter
  • Require careful consideration when calling

Benefits of CQS

Clarity and Confidence

  • Immediately understand method's impact from its signature
  • Return type signals whether state changes
  • Can reason about code more easily
  • Reduces cognitive load

Freedom with Queries

  • Use queries liberally without risk
  • Reorder query calls without concern
  • Introduce new queries anywhere
  • No unintended consequences

Caution with Commands

  • Explicit signal to exercise care
  • Impact on system state is clear
  • Easier to track state changes
  • Better debugging and reasoning

Examples

Good - Follows CQS:

// Query - no side effects
const total = order.getTotal();
const items = cart.getItems();
const isValid = user.isAuthenticated();

// Command - changes state, returns nothing
cart.addItem(item);
user.logout();
order.submit();
```text

**Problematic - Violates CQS:**
```javascript
// Returns value AND changes state
const item = stack.pop(); // Removes and returns
const user = cache.getOrCreate(id); // Might create
const count = list.removeAll(predicate); // Removes and counts
```text

### Practical Considerations

**Exceptions Exist**

Some methods are useful enough to justify breaking the rule:
- `stack.pop()` - Returns and removes (convenience outweighs purity)
- `collection.remove(item)` - Returns success/failure (useful feedback)
- `cache.getOrCreate(key)` - Query with lazy initialization

**Meyer's Guidance:**
Follow the principle when feasible but recognize situations where flexibility matters more than purity.

**Language Support**
- C++ `const` modifier enforces query contracts
- Many languages lack built-in CQS support
- Rely on naming conventions and discipline

### Naming Conventions

**Queries - Use nouns or questions:**
- `getTotal()`, `isValid()`, `hasPermission()`
- `calculatePrice()`, `findUser()`, `countItems()`
- Names suggest information retrieval

**Commands - Use verbs:**
- `setStatus()`, `addItem()`, `removeUser()`
- `update()`, `delete()`, `save()`
- Names suggest action/mutation

### CQS in Different Contexts

**Object-Oriented Design**
- Instance methods separated by query/command
- Helps maintain immutability
- Clearer object contracts

**Functional Programming**
- Pure functions are ultimate queries
- Side effects isolated to specific boundaries
- CQS aligns with functional principles

**API Design**
- GET requests = queries (idempotent, safe)
- POST/PUT/DELETE = commands (state-changing)
- HTTP naturally enforces CQS

**Database Operations**
- SELECT = query
- INSERT/UPDATE/DELETE = commands
- SQL separates concerns explicitly

### CQRS - Taking CQS Further

**Command Query Responsibility Segregation:**
- Extends CQS to architectural level
- Separate models for reads and writes
- Different data stores for queries vs. commands
- Optimized independently

**When to use CQRS:**
- High read/write ratio imbalance
- Different scaling needs for reads vs. writes
- Complex domain with distinct read/write patterns
- Event sourcing architecture

### Common Violations to Watch For

**Hidden Side Effects in Queries**
```javascript
// BAD - getUser() shouldn't log or cache
function getUser(id) {
logAccess(id); // Side effect!
const user = database.find(id);
cache.store(id, user); // Side effect!
return user;
}

// GOOD - pure query
function getUser(id) {
return database.find(id);
}
```text

**Commands Returning Too Much**
```javascript
// BAD - update returns entire object
function updateEmail(userId, email) {
user.email = email;
return user; // Mixing command with query
}

// BETTER - return success/void
function updateEmail(userId, email) {
user.email = email;
// Return nothing or just success indicator
}
```text

### Testing Benefits

**Queries are Easy to Test**
- Pure functions without side effects
- No setup/teardown needed
- No mocking required
- Fast and reliable

**Commands Require More Care**
- Need to verify state changes
- Setup and teardown important
- May require mocks/stubs
- Potentially slower

### CQS and Immutability

**Natural Alignment:**
- Immutable objects only have queries
- Commands return new objects instead of mutating
- Functional programming style
- Thread-safe by default

**Example:**
```javascript
// Immutable style - no commands, only queries
const order = createOrder();
const withItem = order.addItem(item); // Returns new order
const total = withItem.getTotal(); // Query
```text

**Reference:** [Command Query Separation](https://martinfowler.com/bliki/CommandQuerySeparation.html)

## Naming Things - The Ongoing Challenge

> "There are only two hard things in Computer Science: cache invalidation and naming things." (Phil Karlton)

### Why Naming is Hard

**Universal Developer Challenge**
- Affects all skill levels equally
- Requires conveying purpose and behavior concisely
- Balance between precision and brevity
- No single "right" answer

**Long-Lasting Impact**
- Names persist in codebases for years
- Bad names compound confusion over time
- Renaming is expensive (but worthwhile)
- First impression matters

### Principles for Good Naming

**1. Reveal Intention**
- Name should clearly express purpose
- Reader shouldn't need to decipher meaning
- Connects to Beck's Design Rule #2
- Self-documenting code

**Examples:**
```javascript
// BAD
const d = 86400; // What is d?
const temp = getUserData(); // What data?

// GOOD
const secondsPerDay = 86400;
const activeUserProfile = getUserProfile();
```text

**2. Use Domain Language**
- Match business/domain terminology
- Makes code understandable to non-programmers
- Reduces translation between requirements and code
- Supports ubiquitous language (DDD)

**Examples:**
```javascript
// BAD - technical jargon
class RecordProcessor { }
class DataContainer { }

// GOOD - domain language
class InvoiceProcessor { }
class ShoppingCart { }
```text

**3. Be Consistent**
- Same terminology for same concepts
- Follow established patterns in codebase
- Don't use synonyms for identical things
- Predictability helps comprehension

**Examples:**
```javascript
// BAD - inconsistent
getUserData()
fetchClientInfo()
retrieveCustomerDetails() // All do the same thing!

// GOOD - consistent
getUserProfile()
getClientProfile()
getCustomerProfile()
```text

**4. Avoid Meaningless Names**

**Generic names to avoid:**
- `data`, `info`, `item`, `object`
- `manager`, `handler`, `processor`, `helper`
- `doStuff()`, `process()`, `handle()`
- Single letters (except loop counters)

**When generic names are OK:**
- Loop indices: `i`, `j`, `k`
- Generic algorithms: `T` for type parameter
- Established conventions: `e` for error/event

**5. Choose Appropriate Length**

**Context matters:**
- Short scope → shorter names OK
- Long scope → more descriptive names needed
- Frequently used → can be shorter
- Rarely used → should be more descriptive

**Examples:**
```javascript
// Short scope - fine
for (let i = 0; i < items.length; i++) {
const item = items[i];
process(item);
}

// Long scope - needs clarity
class CustomerOrderProcessingService {
processCustomerOrderWithValidation(order) {
// Longer, more descriptive name appropriate
}
}
```text

**6. Avoid Abbreviations**

**Unless they're universally understood:**
```javascript
// BAD
const usrMgr = new UsrMgr();
const calcTot = () => { };

// GOOD
const userManager = new UserManager();
const calculateTotal = () => { };

// OK - widely understood
const html = "<div>...</div>";
const url = "https://...";
const id = user.id;
```text

### Names for Different Constructs

**Classes and Types**
- Nouns or noun phrases
- Describe what it IS
- Examples: `User`, `OrderProcessor`, `PaymentGateway`

**Functions and Methods**
- Verbs or verb phrases
- Describe what it DOES
- Examples: `calculateTotal()`, `sendEmail()`, `validateInput()`

**Boolean Variables/Functions**
- Questions or assertions
- Start with `is`, `has`, `can`, `should`
- Examples: `isValid`, `hasPermission`, `canEdit`, `shouldRetry`

**Constants**
- UPPER_SNAKE_CASE traditionally
- Descriptive of value or purpose
- Examples: `MAX_RETRY_ATTEMPTS`, `DEFAULT_TIMEOUT_MS`

**Collections**
- Plural nouns
- Describe contents
- Examples: `users`, `orders`, `activeConnections`

### Warning Signs of Bad Names

**Need for Explanatory Comments**
```javascript
// BAD - name doesn't explain itself
const x = 5; // Number of retry attempts

// GOOD - self-explanatory
const maxRetryAttempts = 5;
```text

**Different Interpretations**
- Team members understand differently
- Leads to bugs and confusion
- Sign name is too vague or misleading

**Mismatched Name and Behavior**
```javascript
// BAD - does more than name suggests
function getUser(id) {
const user = db.find(id);
logAccess(user); // Surprise!
sendAnalytics(user); // More surprises!
return user;
}

// GOOD - name matches behavior
function getUserWithTracking(id) {
const user = db.find(id);
trackUserAccess(user);
return user;
}
```text

### Refactoring Names

**Don't Fear Renaming**
- Modern IDEs make renaming safe
- Better name improves entire codebase
- Worth the effort for frequently used identifiers
- Gradual improvement over time

**When to Rename**
- Understanding of domain improves
- Original name misleads
- Encountering confusion repeatedly
- During refactoring anyway

**How to Rename Safely**
- Use IDE refactoring tools
- Rename in small commits
- Update tests and documentation
- Use Parallel Change pattern for public APIs

### Semantic Diffusion and Naming

**Be Precise with Technical Terms**
- Don't let terms lose their meaning
- Use established terms correctly
- Define terms when introducing them
- Resist vague buzzwords

**Examples of term precision:**
- "Refactoring" means specific technique (see 07-development-practices.md)
- "Factory" means specific pattern
- "Service" has architectural meaning
- Use terms correctly or choose different words

### Cultural and Team Considerations

**Establish Naming Conventions**
- Document in style guide
- Discuss during code review
- Build shared vocabulary
- Refine over time

**Ubiquitous Language (DDD)**
- Use same terms in code as business uses
- Developers and business speak same language
- Reduces misunderstanding
- Code becomes domain model

**References:**
- [Two Hard Things](https://martinfowler.com/bliki/TwoHardThings.html)
- [Semantic Diffusion](https://martinfowler.com/bliki/SemanticDiffusion.html)

## API Design Principles Summary

### Interface Design Checklist

- [ ] Separate queries (return value, no side effects) from commands (mutate state, no return)
- [ ] Names clearly reveal intention
- [ ] Use domain language consistently
- [ ] Avoid generic names like "manager", "handler", "processor"
- [ ] Boolean functions start with is/has/can/should
- [ ] Method names match their actual behavior
- [ ] No hidden side effects in queries
- [ ] Consider immutability for complex state
- [ ] Document exceptions to CQS when necessary
- [ ] Use appropriate name length for scope

### Common API Design Mistakes

- Query methods with hidden side effects (logging, caching, etc.)
- Commands that return complex objects (mixing concerns)
- Misleading names that don't match behavior
- Inconsistent terminology across API
- Generic names that don't convey meaning
- Overly abbreviated names
- Breaking CQS without good reason
- Not considering caller's perspective