diff --git a/architectures/clean-architecture/readme.md b/architectures/clean-architecture/readme.md index abbc669..6c21e80 100644 --- a/architectures/clean-architecture/readme.md +++ b/architectures/clean-architecture/readme.md @@ -1,17 +1,12 @@ --- -description: Vibe coding guidelines and architectural constraints for Clean Architecture within the Architecture domain. technology: Clean Architecture domain: Architecture level: Senior/Architect version: Agnostic tags: [architecture, system-design, clean-architecture, best-practices] ai_role: Senior Architect -last_updated: 2026-03-22 -topic: Clean Architecture -complexity: Architect -last_evolution: 2026-03-29 -vibe_coding_ready: true--- - +last_updated: 2026-03-29 +---
# 🏛️ Clean Architecture Production-Ready Best Practices @@ -19,16 +14,178 @@ vibe_coding_ready: true--- --- Этот инженерный директив определяет **лучшие практики (best practices)** для архитектуры Clean Architecture. Данный документ спроектирован для обеспечения максимальной масштабируемости, безопасности и качества кода при разработке приложений корпоративного уровня. + # Context & Scope - **Primary Goal:** Предоставить строгие архитектурные правила и практические паттерны для создания масштабируемых систем. - **Description:** A concept created by Robert C. Martin (Uncle Bob). It separates a project into concentric rings. The main rule is the Dependency Rule: dependencies can only point inward. + ## Map of Patterns - 📊 [**Data Flow:** Request and Event Lifecycle](./data-flow.md) - 📁 [**Folder Structure:** Layering logic](./folder-structure.md) - ⚖️ [**Trade-offs:** Pros, Cons, and System Constraints](./trade-offs.md) - 🛠️ [**Implementation Guide:** Code patterns and Anti-patterns](./implementation-guide.md) + ## Core Principles 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 + Infrastructure[Infrastructure] --> InterfaceAdapters[Interface Adapters] + InterfaceAdapters --> UseCases[Use Cases] + UseCases --> Domain[Domain Entities] + + %% 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 Infrastructure component; + class InterfaceAdapters component; + class UseCases component; + class Domain component; +``` + +--- + +## 1. ORM Models Bleeding into Domain + +### ❌ Bad Practice +```typescript +// Domain Entity directly inherits from TypeORM BaseEntity +import { BaseEntity, Column, Entity } from 'typeorm'; + +@Entity() +export class User extends BaseEntity { + @Column() + email: string; + + public updateEmail(newEmail: string): void { + this.email = newEmail; + this.save(); // Hard infrastructure coupling + } +} +``` + +### ⚠️ Problem +The Domain layer (the core of the application) is tightly coupled with a specific third-party ORM library (`TypeORM`). This violates the Dependency Rule. Changing the database technology will require rewriting core business logic. + +### ✅ Best Practice +```typescript +// Pure Domain Entity completely agnostic of infrastructure +export class User { + constructor(private readonly id: string, private email: string) {} + + public updateEmail(newEmail: string): void { + this.email = newEmail; + } +} + +// Infrastructure Layer handles the ORM mapping +@Entity('users') +export class UserTypeOrmEntity { + @PrimaryColumn() + id: string; + + @Column() + email: string; +} +``` + +### 🚀 Solution +Isolate your Domain models from any external libraries. Use Data Mapper patterns in the Infrastructure layer to map between pure Domain entities and ORM-specific models. This ensures your core business logic is portable and testable without a database connection. + +--- + +## 2. Direct Infrastructure Injection into Use Cases + +### ❌ Bad Practice +```typescript +import { S3Client } from 'aws-sdk'; + +export class UploadAvatarUseCase { + constructor(private readonly s3Client: S3Client) {} + + public async execute(file: Buffer): Promise { + // Business logic depends strictly on AWS implementation + const result = await this.s3Client.upload({ Bucket: 'av', Body: file }).promise(); + return result.Location; + } +} +``` + +### ⚠️ Problem +The Use Case (Application layer) depends directly on an external hardware/infrastructure concern (`aws-sdk`). You cannot test this Use Case without mocking AWS S3, and you cannot switch to Azure or Google Cloud without modifying the Use Case. + +### ✅ Best Practice +```typescript +// Application Layer defines the abstraction (Port) +export interface IFileStorageService { + uploadFile(buffer: Buffer): Promise; +} + +// Application Layer Use Case depends on the abstraction +export class UploadAvatarUseCase { + constructor(private readonly storageService: IFileStorageService) {} + + public async execute(file: Buffer): Promise { + return this.storageService.uploadFile(file); + } +} +``` + +### 🚀 Solution +Apply the Dependency Inversion Principle. The Application layer should define abstract interfaces (`Ports`) that dictate what it needs from the outside world. The Infrastructure layer implements these interfaces (`Adapters`). This guarantees true architectural decouple. + +--- + +## 3. Fat Controllers Dictating Business Flow + +### ❌ Bad Practice +```typescript +class UserController { + constructor(private readonly userRepository: IUserRepository) {} + + async registerUser(req: Request, res: Response) { + const { email, password } = req.body; + + // Controller executing business validation and flow + const existing = await this.userRepository.findByEmail(email); + if (existing) { + return res.status(400).send('Email taken'); + } + + const user = new User(email, hash(password)); + await this.userRepository.save(user); + + return res.status(201).send(user); + } +} +``` + +### ⚠️ Problem +The Controller (Interface Adapters layer) contains the business rules. It directly uses the Repository, bypassing the Application Use Case layer. This makes the logic difficult to reuse across different entry points (e.g., CLI, gRPC, HTTP). + +### ✅ Best Practice +```typescript +class UserController { + constructor(private readonly registerUserUseCase: RegisterUserUseCase) {} + + async registerUser(req: Request, res: Response) { + // Controller solely adapts HTTP into Use Case DTOs + const result = await this.registerUserUseCase.execute({ + email: req.body.email, + password: req.body.password + }); + + return res.status(201).send(result); + } +} +``` + +### 🚀 Solution +Controllers should be entirely "dumb". Their only responsibility is to parse incoming requests, pass standard DTOs to the corresponding Use Case, and format the output. All branching business logic and validation must reside inside the independent Use Case interactor. diff --git a/architectures/serverless/readme.md b/architectures/serverless/readme.md index a49235f..e9a7e89 100644 --- a/architectures/serverless/readme.md +++ b/architectures/serverless/readme.md @@ -1,17 +1,12 @@ --- -description: Vibe coding guidelines and architectural constraints for Serverless within the Architecture domain. technology: Serverless domain: Architecture level: Senior/Architect version: Agnostic tags: [architecture, system-design, serverless, best-practices] ai_role: Senior Architect -last_updated: 2026-03-22 -topic: Serverless -complexity: Architect -last_evolution: 2026-03-29 -vibe_coding_ready: true--- - +last_updated: 2026-03-29 +---
# 🏛️ Serverless Production-Ready Best Practices @@ -19,16 +14,133 @@ vibe_coding_ready: true--- --- Этот инженерный директив определяет **лучшие практики (best practices)** для архитектуры Serverless. Данный документ спроектирован для обеспечения максимальной масштабируемости, безопасности и качества кода при разработке приложений корпоративного уровня. + # Context & Scope - **Primary Goal:** Предоставить строгие архитектурные правила и практические паттерны для создания масштабируемых систем. - **Description:** Developers do not manage servers at all. The entire "server" consists of bite-sized pieces of business logic (functions/Lambdas) living in the cloud. + ## Map of Patterns - 📊 [**Data Flow:** Request and Event Lifecycle](./data-flow.md) - 📁 [**Folder Structure:** Layering logic](./folder-structure.md) - ⚖️ [**Trade-offs:** Pros, Cons, and System Constraints](./trade-offs.md) - 🛠️ [**Implementation Guide:** Code patterns and Anti-patterns](./implementation-guide.md) + ## Core Principles 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 + Trigger[Event Trigger] --> Func1[Function 1] + Trigger2[HTTP Request] --> Func2[Function 2] + + %% 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 Func1 component; + class Trigger component; + class Func2 component; + class Trigger2 component; +``` + +--- + +## 1. The Monolithic Lambda (Fat Function) + +### ❌ Bad Practice +```javascript +// A single Lambda function handling multiple distinct routes/actions +exports.handler = async (event) => { + const { path, httpMethod, body } = event; + + if (path === '/users' && httpMethod === 'POST') { + return await createUser(JSON.parse(body)); + } else if (path === '/users' && httpMethod === 'GET') { + return await getUsers(); + } else if (path === '/products' && httpMethod === 'POST') { + return await createProduct(JSON.parse(body)); + } + + return { statusCode: 404, body: 'Not Found' }; +}; +``` + +### ⚠️ Problem +Grouping unrelated endpoints into a single function defeats the purpose of Serverless. It increases the deployment package size (slowing down cold starts), couples business domains together, complicates IAM permissions (granting excessive privileges), and makes individual function metrics/tracing impossible. + +### ✅ Best Practice +```javascript +// One function strictly mapped to one specific capability +// users-create.js +exports.handler = async (event) => { + const payload = JSON.parse(event.body); + return await createUser(payload); +}; + +// users-get.js +exports.handler = async (event) => { + return await getUsers(); +}; +``` + +### 🚀 Solution +Embrace the "Single Responsibility Principle" at the infrastructure level. Decompose logic into granular, single-purpose functions. Use an API Gateway to handle routing rather than parsing paths inside the compute layer. This ensures precise IAM scoping, accurate observability, and isolated failure domains. + +--- + +## 2. Stateful Execution Contexts + +### ❌ Bad Practice +```javascript +// In-memory cache shared across invocations incorrectly +let databaseConnection = null; +let userCache = []; // BAD: State assumes the same instance will be hit + +exports.handler = async (event) => { + if (!databaseConnection) { + databaseConnection = await createConnection(); + } + + if (userCache.length === 0) { + userCache = await databaseConnection.query('SELECT * FROM users'); + } + + // Mutating the array mutates state for concurrent overlapping invocations + userCache.push(JSON.parse(event.body)); + + return { statusCode: 200, body: JSON.stringify(userCache) }; +}; +``` + +### ⚠️ Problem +Serverless environments are ephemeral. You cannot guarantee that subsequent requests will be routed to the same container instance. Relying on local variable state leads to unpredictable race conditions, phantom data bugs, and security leaks across tenant requests. + +### ✅ Best Practice +```javascript +// Safely caching connections, but keeping data strictly stateless +let databaseConnection = null; + +exports.handler = async (event) => { + // Good: Reusing the connection pool across warm starts + if (!databaseConnection) { + databaseConnection = await createConnection(); + } + + // Data state is retrieved fresh or from an external distributed cache + const users = await databaseConnection.query('SELECT * FROM users'); + const newUser = JSON.parse(event.body); + + await databaseConnection.query('INSERT INTO users...', newUser); + + return { statusCode: 200, body: JSON.stringify([...users, newUser]) }; +}; +``` + +### 🚀 Solution +Treat every function invocation as stateless. It is acceptable (and encouraged) to cache static assets or database connection pools globally (outside the handler) to reduce cold start latency. However, all business data, session state, or mutable variables must be strictly scoped to the handler's execution context or offloaded to Redis/DynamoDB.