4.8 KiB
4.8 KiB
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
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
- Solo datos planos: números, cadenas, literales, arrays; nada de lógica.
- Fechas en ISO-8601 UTC (
yyyy-MM-dd'T'HH:mm:ss.SSS'Z'). - Enums expuestos como string literal en
snake_caseoUPPER_SNAKE_CASE; evita números mágicos. - Moneda
- Siempre con la estructura común:
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:
interface InvoiceCreationResultMapper {
toResult(entity: Invoice): InvoiceCreationResultDTO;
}
5. Validación y versiones
-
Validación de entrada
- Usa
class-validatoro Zod en el controller; nunca en el dominio. - Convierte a Value Objects una vez que el DTO pasó la validación.
- Usa
-
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.
- Añade sufijos de versión en el archivo, no en el nombre de la interfaz:
6. Ejemplo completo (creación de factura)
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
// 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 |