codigo0/docs/SEPARACION_CAPAS_LOGICA_NEGOCIO.md

419 lines
13 KiB
Markdown
Raw Normal View History

# 🏗️ Separación de Capas y Organización de Lógica de Negocio
## 🎯 Objetivo
Estudiar y definir cómo separar las capas de la aplicación y organizar la lógica de negocio siguiendo Clean Architecture.
---
## 📐 Arquitectura en Capas
### Estructura Propuesta
```
┌─────────────────────────────────────────────────────────┐
│ PRESENTATION LAYER (Routes) │
│ - Express Routes │
│ - Middleware (auth, validation, rate-limit) │
│ - Request/Response DTOs │
│ - NO lógica de negocio │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ APPLICATION LAYER (Use Cases) │
│ - Services (orquestación) │
│ - Use Cases (casos de uso específicos) │
│ - DTOs de aplicación │
│ - Validaciones de aplicación │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ DOMAIN LAYER (Core) │
│ - Entities (entidades de negocio) │
│ - Value Objects (objetos de valor) │
│ - Domain Services (lógica de dominio pura) │
│ - Repository Interfaces (contratos) │
│ - Domain Events │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ INFRASTRUCTURE LAYER (Implementación) │
│ - Repository Implementations │
│ - Database Access │
│ - File Storage │
│ - External Services │
│ - Cache │
└─────────────────────────────────────────────────────────┘
```
---
## 📁 Organización de Carpetas
```
backend/src/
├── domain/ # 🎯 DOMAIN LAYER
│ ├── entities/ # Entidades de negocio
│ │ ├── ContentItem.ts
│ │ ├── Drug.ts
│ │ ├── Protocol.ts
│ │ ├── GlossaryTerm.ts
│ │ └── MedicalReview.ts
│ │
│ ├── value-objects/ # Objetos de valor inmutables
│ │ ├── ContentStatus.ts
│ │ ├── ContentPriority.ts
│ │ ├── DoseRange.ts
│ │ ├── PatientAge.ts
│ │ ├── PatientWeight.ts
│ │ └── Version.ts
│ │
│ ├── services/ # Servicios de dominio
│ │ ├── CriticalErrorDetector.ts
│ │ ├── DoseCalculator.ts
│ │ └── ProtocolValidator.ts
│ │
│ ├── repositories/ # Interfaces de repositorios
│ │ ├── IContentRepository.ts
│ │ ├── IDrugRepository.ts
│ │ ├── IGlossaryRepository.ts
│ │ └── IMediaRepository.ts
│ │
│ └── events/ # Eventos de dominio
│ ├── ContentSubmitted.ts
│ ├── ContentApproved.ts
│ └── ContentPublished.ts
├── application/ # 🔧 APPLICATION LAYER
│ ├── services/ # Servicios de aplicación
│ │ ├── ContentService.ts
│ │ ├── DrugService.ts
│ │ ├── DoseValidationService.ts
│ │ ├── ProtocolValidationService.ts
│ │ ├── MedicalValidationService.ts
│ │ └── MediaService.ts
│ │
│ ├── use-cases/ # Casos de uso específicos
│ │ ├── content/
│ │ │ ├── CreateContentUseCase.ts
│ │ │ ├── UpdateContentUseCase.ts
│ │ │ ├── SubmitForReviewUseCase.ts
│ │ │ └── PublishContentUseCase.ts
│ │ │
│ │ ├── drugs/
│ │ │ ├── CalculateDoseUseCase.ts
│ │ │ ├── ValidateDoseUseCase.ts
│ │ │ └── CreateDrugUseCase.ts
│ │ │
│ │ └── protocols/
│ │ ├── ExecuteProtocolUseCase.ts
│ │ └── ValidateProtocolSequenceUseCase.ts
│ │
│ └── dto/ # Data Transfer Objects
│ ├── CreateContentDTO.ts
│ ├── UpdateContentDTO.ts
│ └── DoseCalculationDTO.ts
├── infrastructure/ # 🔌 INFRASTRUCTURE LAYER
│ ├── repositories/ # Implementaciones de repositorios
│ │ ├── ContentRepository.ts
│ │ ├── DrugRepository.ts
│ │ ├── GlossaryRepository.ts
│ │ └── MediaRepository.ts
│ │
│ ├── database/ # Acceso a base de datos
│ │ ├── Database.ts
│ │ ├── migrations/
│ │ └── queries/
│ │
│ ├── storage/ # Almacenamiento de archivos
│ │ ├── FileStorage.ts
│ │ └── MediaStorage.ts
│ │
│ ├── cache/ # Sistema de caché
│ │ └── CacheService.ts
│ │
│ └── external/ # Servicios externos
│ └── NotificationService.ts
└── presentation/ # 🌐 PRESENTATION LAYER
├── routes/ # Rutas Express
│ ├── content.ts
│ ├── drugs.ts
│ ├── glossary.ts
│ ├── media.ts
│ └── validation.ts
├── middleware/ # Middleware
│ ├── auth.ts
│ ├── validate.ts
│ └── rate-limit.ts
└── validators/ # Validadores Zod
├── content.ts
├── drugs.ts
└── glossary.ts
```
---
## 🔄 Flujo de Datos
### Ejemplo: Crear Contenido
```
1. Request → Route (presentation/routes/content.ts)
2. Validar con Zod (presentation/validators/content.ts)
3. Llamar Use Case (application/use-cases/content/CreateContentUseCase.ts)
4. Use Case llama Service (application/services/ContentService.ts)
5. Service usa Domain Entities (domain/entities/ContentItem.ts)
6. Service llama Repository Interface (domain/repositories/IContentRepository.ts)
7. Repository Implementation (infrastructure/repositories/ContentRepository.ts)
8. Database (infrastructure/database/Database.ts)
9. Response ← Route
```
---
## 📝 Reglas de Separación
### ✅ Domain Layer
- **SÍ:** Entidades, Value Objects, Lógica de negocio pura
- **SÍ:** Interfaces de repositorios
- **NO:** Acceso a base de datos
- **NO:** Dependencias externas
- **NO:** Frameworks (Express, etc.)
### ✅ Application Layer
- **SÍ:** Casos de uso, Orquestación
- **SÍ:** Validaciones de aplicación
- **SÍ:** DTOs de aplicación
- **NO:** Lógica de dominio (debe estar en Domain)
- **NO:** Acceso directo a base de datos
### ✅ Infrastructure Layer
- **SÍ:** Implementaciones de repositorios
- **SÍ:** Acceso a base de datos
- **SÍ:** Servicios externos
- **NO:** Lógica de negocio
- **NO:** Validaciones de dominio
### ✅ Presentation Layer
- **SÍ:** Routes, Middleware
- **SÍ:** Validación de entrada (Zod)
- **SÍ:** Transformación Request/Response
- **NO:** Lógica de negocio
- **NO:** Acceso directo a repositorios
---
## 🎯 Ejemplo de Implementación
### Domain Entity (Inmutable)
```typescript
// domain/entities/ContentItem.ts
export class ContentItem {
private constructor(
readonly id: string,
readonly type: ContentType,
readonly title: string,
readonly status: ContentStatus,
readonly createdAt: Date,
readonly updatedAt: Date
) {}
static create(
id: string,
type: ContentType,
title: string
): ContentItem {
// Validaciones de dominio
if (!title || title.trim().length === 0) {
throw new Error('Título es obligatorio');
}
return new ContentItem(
id,
type,
title,
ContentStatus.DRAFT,
new Date(),
new Date()
);
}
submitForReview(): ContentItem {
if (this.status !== ContentStatus.DRAFT) {
throw new Error('Solo contenido en borrador puede enviarse a revisión');
}
return new ContentItem(
this.id,
this.type,
this.title,
ContentStatus.IN_REVIEW,
this.createdAt,
new Date()
);
}
// Inmutable: siempre retorna nueva instancia
}
```
### Use Case
```typescript
// application/use-cases/content/CreateContentUseCase.ts
export class CreateContentUseCase {
constructor(
private readonly contentRepository: IContentRepository,
private readonly validator: ContentValidator
) {}
async execute(input: CreateContentDTO): Promise<ContentItem> {
// 1. Validar entrada
this.validator.validateCreate(input);
// 2. Crear entidad de dominio
const content = ContentItem.create(
crypto.randomUUID(),
input.type,
input.title
);
// 3. Persistir
await this.contentRepository.save(content);
// 4. Retornar
return content;
}
}
```
### Repository Interface (Domain)
```typescript
// domain/repositories/IContentRepository.ts
export interface IContentRepository {
findById(id: string): Promise<ContentItem | null>;
findAll(filters: ContentFilters): Promise<ContentItem[]>;
save(content: ContentItem): Promise<ContentItem>;
delete(id: string): Promise<void>;
}
```
### Repository Implementation (Infrastructure)
```typescript
// infrastructure/repositories/ContentRepository.ts
export class ContentRepository implements IContentRepository {
constructor(private readonly db: Database) {}
async findById(id: string): Promise<ContentItem | null> {
const result = await this.db.query(
'SELECT * FROM tes_content.content_items WHERE id = $1',
[id]
);
if (result.rows.length === 0) return null;
return this.mapToDomain(result.rows[0]);
}
async save(content: ContentItem): Promise<ContentItem> {
// Mapear de dominio a persistencia
const row = this.mapToPersistence(content);
await this.db.query(
`INSERT INTO tes_content.content_items (...) VALUES (...)`,
[row]
);
return content;
}
private mapToDomain(row: any): ContentItem {
// Mapear de persistencia a dominio
}
private mapToPersistence(content: ContentItem): any {
// Mapear de dominio a persistencia
}
}
```
### Route (Presentation)
```typescript
// presentation/routes/content.ts
router.post(
'/',
authenticate,
requirePermission('content:write'),
validateBody(createContentSchema),
async (req: AuthRequest, res: Response) => {
try {
const useCase = new CreateContentUseCase(
contentRepository,
contentValidator
);
const content = await useCase.execute(req.body);
res.status(201).json({
id: content.id,
title: content.title,
status: content.status.toString()
});
} catch (error) {
handleError(error, res);
}
}
);
```
---
## 🔒 Reglas de Dependencias
### Regla de Dependencia
- **Domain:** No depende de nadie
- **Application:** Solo depende de Domain
- **Infrastructure:** Depende de Domain y Application
- **Presentation:** Depende de Application y Domain
### Diagrama de Dependencias
```
Presentation → Application → Domain
↓ ↓
Infrastructure → Domain
```
---
## ✅ Checklist de Separación
Al crear nuevo código, verificar:
- [ ] ¿Está en la capa correcta?
- [ ] ¿Depende solo de capas inferiores?
- [ ] ¿La lógica de negocio está en Domain?
- [ ] ¿Los casos de uso están en Application?
- [ ] ¿Las implementaciones están en Infrastructure?
- [ ] ¿Las rutas están en Presentation?
---
**Fin del documento**