177 lines
9.4 KiB
Markdown
177 lines
9.4 KiB
Markdown
# Arquitectura backend
|
|
|
|
> Referencia especializada del skill `rdx-solid`. Consulta primero [arquitectura.md](arquitectura.md) y aplica las convenciones de `SKILL.md` y `AGENTS.md` del proyecto.
|
|
|
|
## 1. Propósito y alcance
|
|
|
|
Este documento orienta el diseño y evolución de servicios backend, APIs, procesos asíncronos, persistencia e integraciones. Está pensado principalmente para TypeScript y Node.js, pero sus principios son transferibles a otros entornos.
|
|
|
|
**Objetivo:** construir servicios correctos, seguros, verificables y mantenibles con una complejidad proporcional a sus requisitos. No impongas arquitecturas hexagonales, DDD ni una capa por concepto cuando no aporten valor.
|
|
|
|
## 2. Reconocer la arquitectura existente
|
|
|
|
Antes de modificar código:
|
|
|
|
1. Identifica framework, estructura modular, puntos de entrada y convenciones.
|
|
2. Localiza los casos de uso, modelos, contratos y consumidores afectados.
|
|
3. Examina gestión de errores, autenticación, persistencia y pruebas existentes.
|
|
4. Comprueba si el cambio afecta a contratos públicos, datos persistidos o transacciones.
|
|
5. Limita las modificaciones al ámbito de la tarea.
|
|
|
|
Para sistemas heredados, consulta además [arquitectura-legacy.md](arquitectura-legacy.md). Para decisiones estructurales importantes, consulta [arquitectura-decisiones.md](arquitectura-decisiones.md).
|
|
|
|
## 3. Organización modular y responsabilidades
|
|
|
|
Prefiere módulos funcionales cuando faciliten la cohesión, pero conserva la organización del proyecto si ya resulta adecuada. Un módulo puede combinar presentación, aplicación, dominio e infraestructura sin exigir directorios físicos para todas las capas.
|
|
|
|
```text
|
|
src/
|
|
modules/
|
|
orders/
|
|
order-routes.ts
|
|
create-order.ts
|
|
order.ts
|
|
order-repository.ts
|
|
postgres-order-repository.ts
|
|
order-contracts.ts
|
|
customers/
|
|
...
|
|
infrastructure/
|
|
database/
|
|
logging/
|
|
```
|
|
|
|
Esta distribución es ilustrativa. Divide un módulo cuando su tamaño o responsabilidades reales lo justifiquen, no porque falten directorios de una plantilla.
|
|
|
|
- **Transporte/presentación:** adapta HTTP, CLI, mensajería o RPC a entradas y respuestas de aplicación.
|
|
- **Aplicación:** coordina casos de uso, autorización específica y límites transaccionales.
|
|
- **Dominio:** aplica reglas, estados e invariantes.
|
|
- **Infraestructura:** implementa persistencia, proveedores e integraciones externas.
|
|
|
|
Evita controladores que contengan toda la lógica de negocio y servicios que solo deleguen a otros servicios sin responsabilidad concreta. No coloques en `shared` reglas de negocio cuya propiedad corresponde a un módulo.
|
|
|
|
## 4. APIs y contratos
|
|
|
|
- Diseña endpoints y operaciones alrededor de recursos y casos de uso comprensibles.
|
|
- Define formatos de entrada, salida y error explícitos; no expongas entidades de base de datos por conveniencia.
|
|
- Valida datos no confiables en el límite de entrada. Los tipos TypeScript no validan JSON en ejecución.
|
|
- Utiliza códigos HTTP, semántica de errores y cabeceras apropiados; no devuelvas éxito para ocultar fallos.
|
|
- Evalúa paginación, límites, filtrado y orden estable en listados potencialmente grandes.
|
|
- Considera versionado y compatibilidad cuando haya consumidores independientes.
|
|
- No presupongas que frontend y backend se despliegan simultáneamente.
|
|
|
|
```typescript
|
|
interface CreateOrderRequest {
|
|
customerId: string;
|
|
items: Array<{ productId: string; quantity: number }>;
|
|
}
|
|
|
|
async function createOrderHandler(request: HttpRequest): Promise<HttpResponse> {
|
|
const input = createOrderSchema.parse(request.body);
|
|
const result = await createOrder.execute(input);
|
|
return { status: 201, body: { orderId: result.orderId } };
|
|
}
|
|
```
|
|
|
|
El esquema, `HttpRequest`, `HttpResponse` y el caso de uso son colaboradores ilustrativos. Adapta los contratos a las herramientas del proyecto y no introduzcas validadores duplicados sin necesidad.
|
|
|
|
Consulta [arquitectura-shared.md](arquitectura-shared.md) al publicar tipos o esquemas consumidos por frontend.
|
|
|
|
## 5. Persistencia y transacciones
|
|
|
|
- Modela el acceso a datos según las necesidades del dominio; no crees repositorios genéricos de forma automática.
|
|
- Mantén SQL y detalles de drivers fuera de las reglas de negocio cuando su acoplamiento dificulte evolucionar el sistema.
|
|
- Utiliza transacciones cuando varias escrituras deban confirmarse o revertirse como una unidad.
|
|
- Diseña restricciones de integridad también en la base de datos cuando corresponda.
|
|
- Define migraciones reproducibles, compatibles y con procedimientos de recuperación cuando el riesgo lo exija.
|
|
- Evita consultas N+1, cargas excesivas e índices especulativos: mide y evalúa el uso real.
|
|
- No prometas atomicidad entre base de datos y servicios externos sin un mecanismo que la garantice.
|
|
|
|
```typescript
|
|
async function confirmOrder(orderId: OrderId): Promise<void> {
|
|
await database.transaction(async (tx) => {
|
|
const order = await orderRepository.findForUpdate(tx, orderId);
|
|
order.confirm();
|
|
await orderRepository.save(tx, order);
|
|
await outboxRepository.enqueue(tx, {
|
|
type: 'ORDER_CONFIRMED',
|
|
orderId: orderId.value,
|
|
});
|
|
});
|
|
}
|
|
```
|
|
|
|
El ejemplo requiere implementaciones transaccionales compatibles. Outbox es útil cuando hay que publicar eventos fiables tras una confirmación, pero no es obligatorio en sistemas que no lo necesitan.
|
|
|
|
## 6. Seguridad y aislamiento
|
|
|
|
- Distingue **autenticación** (identidad) de **autorización** (permiso para una operación o recurso).
|
|
- Autoriza en el servidor, incluso si la interfaz oculta acciones no permitidas.
|
|
- En sistemas multiempresa, comprueba el ámbito de datos y permisos en cada operación relevante; no confíes en un `tenantId` enviado por el cliente sin validación.
|
|
- Usa consultas parametrizadas y controles contra inyección.
|
|
- Protege secretos mediante mecanismos adecuados; nunca los incluyas en código ni registros.
|
|
- Valida límites de tamaño, cargas, archivos y tipos de contenido.
|
|
- Aplica privilegio mínimo a cuentas técnicas y conexiones.
|
|
- Gestiona información sensible y errores sin filtrar datos internos.
|
|
|
|
No elimines controles existentes para simplificar los casos de uso o las pruebas.
|
|
|
|
## 7. Asincronía, concurrencia y resiliencia
|
|
|
|
- Usa correctamente `async/await`; evita promesas no esperadas salvo trabajo explícitamente desacoplado y supervisado.
|
|
- Establece plazos de espera, cancelación y tratamiento de fallos en llamadas remotas.
|
|
- Diseña reintentos solo para operaciones seguras o idempotentes, con límites y política de espera.
|
|
- Considera claves de idempotencia para operaciones que puedan repetirse por red o colas.
|
|
- Evita operaciones bloqueantes costosas en el hilo de eventos de Node.js.
|
|
- Define consistencia, orden, duplicados y errores para consumidores de mensajes.
|
|
- Protege cambios concurrentes cuando pueden vulnerar invariantes (bloqueos, versiones u otras técnicas adecuadas).
|
|
- Distingue fallos transitorios de fallos permanentes; no reintentes indiscriminadamente.
|
|
|
|
## 8. Observabilidad y operación
|
|
|
|
- Usa logging estructurado donde aporte utilidad diagnóstica.
|
|
- Conserva identificadores de correlación cuando existan flujos entre servicios.
|
|
- Expón métricas y trazas proporcionales a la criticidad y operación.
|
|
- Prepara comprobaciones de salud que reflejen correctamente el estado del servicio.
|
|
- Cierra conexiones y recursos de forma controlada.
|
|
- Evita registrar credenciales, tokens, datos personales o grandes cargas completas.
|
|
- Define procedimientos de despliegue, migración y reversión cuando correspondan.
|
|
|
|
## 9. Pruebas y verificación
|
|
|
|
Elige pruebas según el riesgo, no por cuotas fijas:
|
|
|
|
- **Dominio:** reglas, invariantes y casos límite mediante pruebas rápidas.
|
|
- **Aplicación:** coordinación, permisos, tratamiento de errores y transacciones.
|
|
- **Infraestructura:** integración real con base de datos, consultas y adaptadores.
|
|
- **API:** validación, autenticación, autorización y contratos.
|
|
- **E2E:** flujos críticos y efectos entre componentes.
|
|
|
|
Prueba especialmente los casos de concurrencia, idempotencia y fallo parcial cuando existan. Consulta [testing.md](testing.md) y [tdd.md](tdd.md).
|
|
|
|
## 10. Señales de alerta
|
|
|
|
- Controladores con reglas de negocio extensas.
|
|
- Entidades que importan directamente controladores o detalles de HTTP.
|
|
- Transacciones repartidas entre operaciones sin un responsable definido.
|
|
- Servicios que acceden a datos de otra empresa sin comprobar ámbito y permisos.
|
|
- Promesas sin supervisión y errores capturados y descartados.
|
|
- Operaciones de pago, envío o notificación repetibles sin garantías definidas.
|
|
- Repositorios, interfaces y capas creados únicamente por plantilla.
|
|
- Un cambio pequeño que obliga a modificar muchos módulos sin motivo real.
|
|
|
|
Cada señal exige análisis, no refactorización automática.
|
|
|
|
## 11. Lista de comprobación
|
|
|
|
- [ ] ¿He identificado el contrato y los consumidores afectados?
|
|
- [ ] ¿Las reglas de negocio están en el componente apropiado?
|
|
- [ ] ¿Las entradas externas se validan en ejecución?
|
|
- [ ] ¿La autorización se comprueba en el servidor y en el ámbito correcto?
|
|
- [ ] ¿Las escrituras y llamadas externas tienen garantías claras ante errores?
|
|
- [ ] ¿He considerado concurrencia e idempotencia cuando corresponden?
|
|
- [ ] ¿Se respeta el alcance y la estructura del proyecto?
|
|
- [ ] ¿Se han ejecutado las verificaciones pertinentes y comunicado las pendientes?
|
|
|
|
**Principio final:** el backend debe preservar invariantes, contratos y seguridad con el mínimo acoplamiento y complejidad justificados.
|