Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions architectures/cqrs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,76 @@ This engineering directive defines the **best practices** for the CQRS (Command
1. **Isolation & Testability:** Changing a single feature doesn't break the entire business process.
2. **Strict Boundaries:** Enforce rigid structural barriers between business logic and infrastructure.
3. **Decoupling:** Decouple how data is stored from how it is queried and displayed.

## Architecture Diagram

```mermaid
graph LR
UI --> Command[Command]
Command --> WriteDB[(Write DB)]
UI --> Query[Query]
Query --> ReadDB[(Read DB)]
WriteDB -. sync .-> ReadDB

%% Added Design Token Styles for Mermaid Diagrams
classDef default fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px,color:#000;
classDef component fill:#e8f5e9,stroke:#4caf50,stroke-width:2px,color:#000;
classDef layout fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px,color:#000;

class WriteDB component;
class Query component;
class ReadDB component;
class Command component;
class UI component;
```

---

## 1. Mixing Reads and Writes in a Monolithic Service

### ❌ Bad Practice
```typescript
class UserService {
constructor(private readonly db: Database) {}

async createUser(data: unknown): Promise<void> {
const user = new User(data);
await this.db.users.save(user);
}

async getActiveUsersWithComplexFilters(filters: unknown): Promise<unknown[]> {
// Complex query hitting the same DB used for writes
return this.db.users.find({ active: true, ...filters }).populate('relations');
}
}
```

### ⚠️ Problem
Combining complex read queries and heavy write operations in a single service and database leads to severe performance bottlenecks. Scaling writes and reads independently is impossible, and the Domain logic for commands becomes entangled with data-shaping logic for queries.

### ✅ Best Practice
```typescript
// --- COMMAND SIDE ---
class CreateUserCommandHandler {
constructor(private readonly writeDb: WriteDatabase) {}

async execute(command: CreateUserCommand): Promise<void> {
const user = User.create(command.data);
await this.writeDb.users.save(user);
// Publish event for ReadDB sync
}
}

// --- QUERY SIDE ---
class GetActiveUsersQueryHandler {
constructor(private readonly readDb: ReadDatabase) {}

async execute(query: GetActiveUsersQuery): Promise<unknown[]> {
// Directly querying an optimized, denormalized read database (e.g., ElasticSearch, Redis)
return this.readDb.users.find({ active: true, ...query.filters });
}
}
```

### 🚀 Solution
Strictly segregate Commands (mutations) from Queries (reads). Use separate models and potentially separate databases optimized for each task. Commands encapsulate complex business logic and write to a normalized DB. Queries bypass complex domain models, reading directly from a denormalized, blazing-fast Read DB, allowing deterministic scaling for read-heavy workloads.
87 changes: 87 additions & 0 deletions architectures/domain-driven-design/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,90 @@ last_updated: 2026-03-22
1. **Isolation & Testability:** Changing a single feature doesn't break the entire business process.
2. **Strict Boundaries:** Enforce rigid structural barriers between business logic and infrastructure.
3. **Decoupling:** Decouple how data is stored from how it is queried and displayed.

## Architecture Diagram

```mermaid
graph TD
Context1[Identity & Access] --> C1Domain[Domain]
Context1 --> C1App[Application]
Context1 --> C1Infra[Infrastructure]

%% Added Design Token Styles for Mermaid Diagrams
classDef default fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px,color:#000;
classDef component fill:#e8f5e9,stroke:#4caf50,stroke-width:2px,color:#000;
classDef layout fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px,color:#000;

class C1Infra component;
class C1Domain component;
class Context1 component;
class C1App component;
```

---

## 1. Anemic Domain Models

### ❌ Bad Practice
```typescript
// Anemic Domain Entity - Just a data bag
class Order {
public status: string;
public totalAmount: number;
}

// Business logic lives entirely in a bloated service
class OrderService {
public payOrder(order: Order, amount: number): void {
if (order.status !== 'PENDING') {
throw new Error('Order cannot be paid');
}
if (amount < order.totalAmount) {
throw new Error('Insufficient amount');
}
order.status = 'PAID';
}
}
```

### ⚠️ Problem
Using Anemic Domain Models breaks the core principle of Domain-Driven Design. The Entity (`Order`) is reduced to a pure data structure without behavior. The business rules ("How does an order get paid?") leak into the `OrderService`, causing logic duplication, difficult testing, and poor encapsulation. The Domain becomes passive.

### ✅ Best Practice
```typescript
// Rich Domain Entity - Encapsulates both data and behavior
class Order {
private status: 'PENDING' | 'PAID' | 'CANCELLED';
private totalAmount: number;

constructor(amount: number) {
this.totalAmount = amount;
this.status = 'PENDING';
}

// Behavior resides inside the Entity
public pay(amount: number): void {
if (this.status !== 'PENDING') {
throw new Error('MANDATORY: Order must be in PENDING state to be paid');
}
if (amount < this.totalAmount) {
throw new Error('MANDATORY: Payment amount must cover the total order amount');
}
this.status = 'PAID';
}
}

// Service merely acts as an orchestrator
class OrderApplicationService {
constructor(private readonly orderRepository: IOrderRepository) {}

public async processPayment(orderId: string, amount: number): Promise<void> {
const order = await this.orderRepository.findById(orderId);
order.pay(amount); // Delegating domain logic to the Entity
await this.orderRepository.save(order);
}
}
```

### 🚀 Solution
MANDATORY: Always implement Rich Domain Models. Entities and Aggregates MUST encapsulate both state and the business rules that mutate that state. Services should be relegated to application orchestration (fetching data, calling entity methods, and saving data), ensuring that invariant business logic is securely locked inside the Domain layer.
Loading