diff --git a/architectures/cqrs/readme.md b/architectures/cqrs/readme.md index 00cf7c5..d26b869 100644 --- a/architectures/cqrs/readme.md +++ b/architectures/cqrs/readme.md @@ -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 { + const user = new User(data); + await this.db.users.save(user); + } + + async getActiveUsersWithComplexFilters(filters: unknown): Promise { + // 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 { + 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 { + // 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. diff --git a/architectures/domain-driven-design/readme.md b/architectures/domain-driven-design/readme.md index e60a7e9..7d8b3c8 100644 --- a/architectures/domain-driven-design/readme.md +++ b/architectures/domain-driven-design/readme.md @@ -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 { + 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.