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
171 changes: 164 additions & 7 deletions architectures/clean-architecture/readme.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,191 @@
---
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
---

<div align="center">
# 🏛️ Clean Architecture Production-Ready Best Practices
</div>
---

Этот инженерный директив определяет **лучшие практики (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<string> {
// 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<string>;
}

// Application Layer Use Case depends on the abstraction
export class UploadAvatarUseCase {
constructor(private readonly storageService: IFileStorageService) {}

public async execute(file: Buffer): Promise<string> {
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.
126 changes: 119 additions & 7 deletions architectures/serverless/readme.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,146 @@
---
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
---

<div align="center">
# 🏛️ Serverless Production-Ready Best Practices
</div>
---

Этот инженерный директив определяет **лучшие практики (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.
Loading