151 lines
4.8 KiB
Markdown
151 lines
4.8 KiB
Markdown
|
|
# Guía de estilo — DTOs y organización de carpetas
|
||
|
|
|
||
|
|
> **Objetivo:** asegurar que **todos** los equipos usen un vocabulario y una estructura de ficheros idéntica al modelar, versionar y serializar los *Data Transfer Objects* (DTO).
|
||
|
|
> Esta guía aplica a cualquier API HTTP/JSON implementada en Express + TypeScript que siga DDD, SOLID y CQRS.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 1. Estructura de carpetas obligatoria
|
||
|
|
|
||
|
|
```text
|
||
|
|
src/
|
||
|
|
└─ <bounded-context>/ (ej. billing/)
|
||
|
|
└─ api/
|
||
|
|
└─ dto/
|
||
|
|
├─ common/ ← Tipos reutilizables (MoneyDTO, AddressDTO…)
|
||
|
|
├─ request/ ← Sólo comandos y queries
|
||
|
|
│ ├─ *.command.dto.ts
|
||
|
|
│ └─ *.query.dto.ts
|
||
|
|
│
|
||
|
|
└─ response/ ← Sólo resultados/vistas
|
||
|
|
├─ *.result.dto.ts
|
||
|
|
└─ *.view.dto.ts
|
||
|
|
|
||
|
|
```
|
||
|
|
|
||
|
|
*Alias TS recomendado*: `@<context>/dto/*` → `src/<context>/api/dto/*`.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. Convención de nombres
|
||
|
|
|
||
|
|
| Categoría | Sufijo **obligatorio** | Descripción & ejemplos |
|
||
|
|
|-----------|------------------------|------------------------|
|
||
|
|
| **Comandos** (mutaciones) | `…CommandDTO` | `CreateInvoiceCommandDTO`, `UpdateInvoiceCommandDTO`, `DeleteInvoiceCommandDTO`, `ChangeInvoiceStatusCommandDTO` |
|
||
|
|
| **Queries** (lecturas con filtros) | `…QueryDTO` | `ListInvoicesQueryDTO`, `GetInvoiceByIdQueryDTO` |
|
||
|
|
| **Resultados** (respuesta de comandos) | `…ResultDTO` | `InvoiceCreationResultDTO`, `InvoiceDeletionResultDTO` |
|
||
|
|
| **Vistas** (respuesta de queries) | `…ViewDTO` | `InvoiceViewDTO`, `InvoiceSummaryViewDTO` |
|
||
|
|
| **Tipos comunes** | `…DTO` | `MoneyDTO`, `PaginationMetaDTO`, `AddressDTO` |
|
||
|
|
|
||
|
|
*Regla de oro:* **No existe ningún DTO sin sufijo, salvo los tipos comunes dentro de `common/`.**
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. Reglas de contenido
|
||
|
|
|
||
|
|
1. **Solo datos planos**: números, cadenas, literales, arrays; nada de lógica.
|
||
|
|
2. **Fechas** en ISO-8601 UTC (`yyyy-MM-dd'T'HH:mm:ss.SSS'Z'`).
|
||
|
|
3. **Enums** expuestos como _string literal_ en `snake_case` o `UPPER_SNAKE_CASE`; evita números mágicos.
|
||
|
|
4. **Moneda**
|
||
|
|
- **Siempre** con la estructura común:
|
||
|
|
|
||
|
|
```ts
|
||
|
|
export interface MoneyDTO {
|
||
|
|
amount: number | null; // unidades mínimas (ej. céntimos)
|
||
|
|
scale: number; // nº de decimales (2 = céntimos)
|
||
|
|
currency_code: string; // ISO-4217 (“EUR”)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
- Se importa desde `dto/common/money.dto.ts`.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. Guía de mapeo
|
||
|
|
|
||
|
|
| Dirección | Componente responsable | Ubicación |
|
||
|
|
|-----------|------------------------|-----------|
|
||
|
|
| **DTO → Dominio** | `…CommandMapper` / `…QueryMapper` | `src/<context>/application/mappers/` |
|
||
|
|
| **Dominio → DTO** | `…ResultMapper` / `…ViewMapper` | mismo directorio |
|
||
|
|
|
||
|
|
Cada mapper implementa **una** función pública:
|
||
|
|
|
||
|
|
```ts
|
||
|
|
interface InvoiceCreationResultMapper {
|
||
|
|
toResult(entity: Invoice): InvoiceCreationResultDTO;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 5. Validación y versiones
|
||
|
|
|
||
|
|
1. **Validación de entrada**
|
||
|
|
- Usa `class-validator` o Zod en el *controller*; nunca en el dominio.
|
||
|
|
- Convierte a Value Objects una vez que el DTO pasó la validación.
|
||
|
|
|
||
|
|
2. **Versionado**
|
||
|
|
- Añade sufijos de versión en el **archivo**, no en el nombre de la interfaz:
|
||
|
|
`invoice.view.v2.dto.ts` → `export interface InvoiceViewV2DTO { … }`
|
||
|
|
- Mantén las versiones anteriores durante **≥1 release** o hasta que los consumidores migren.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 6. Ejemplo completo (creación de factura)
|
||
|
|
|
||
|
|
```text
|
||
|
|
billing/
|
||
|
|
├─ api/
|
||
|
|
│ └─ dto/
|
||
|
|
│ ├─ input/create-invoice.command.dto.ts
|
||
|
|
│ ├─ output/invoice-creation.result.dto.ts
|
||
|
|
│ └─ common/money.dto.ts
|
||
|
|
└─ application/
|
||
|
|
└─ mappers/
|
||
|
|
└─ invoice-creation.result.mapper.ts
|
||
|
|
```
|
||
|
|
|
||
|
|
```ts
|
||
|
|
// create-invoice.command.dto.ts
|
||
|
|
export interface CreateInvoiceCommandDTO {
|
||
|
|
customerId: string;
|
||
|
|
issueDate: string;
|
||
|
|
lines: ReadonlyArray<{
|
||
|
|
description: string;
|
||
|
|
quantity: number;
|
||
|
|
unitPrice: MoneyDTO;
|
||
|
|
}>;
|
||
|
|
}
|
||
|
|
|
||
|
|
// invoice-creation.result.dto.ts
|
||
|
|
export interface InvoiceCreationResultDTO {
|
||
|
|
invoiceId: string;
|
||
|
|
number: string;
|
||
|
|
totalAmount: MoneyDTO;
|
||
|
|
createdAt: string;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 7. Checklist antes de hacer *merge*
|
||
|
|
|
||
|
|
- [ ] Archivo ubicado en la carpeta correcta.
|
||
|
|
- [ ] Sufijo conforme (**CommandDTO**, **QueryDTO**, **ResultDTO**, **ViewDTO**).
|
||
|
|
- [ ] Todos los importes usan `MoneyDTO`.
|
||
|
|
- [ ] Tipos opcionales marcados con `?` y comentados.
|
||
|
|
- [ ] **Sin** lógica, constructores ni métodos.
|
||
|
|
- [ ] PR incluye al menos un test de mapper (input ⇄ dominio ⇄ output).
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 8. Tabla resumen
|
||
|
|
|
||
|
|
| Carpeta | Sufijo | Ejemplo clásico |
|
||
|
|
|---------|--------|-----------------|
|
||
|
|
| `dto/input/` | `CommandDTO` | `DeleteInvoiceCommandDTO` |
|
||
|
|
| `dto/input/` | `QueryDTO` | `ListInvoicesQueryDTO` |
|
||
|
|
| `dto/output/` | `ResultDTO` | `InvoiceDeletionResultDTO` |
|
||
|
|
| `dto/output/` | `ViewDTO` | `InvoiceSummaryViewDTO` |
|
||
|
|
| `dto/common/` | `DTO` | `MoneyDTO` |
|