9.4 KiB
Arquitectura backend
Referencia especializada del skill
rdx-solid. Consulta primero arquitectura.md y aplica las convenciones deSKILL.mdyAGENTS.mddel 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:
- Identifica framework, estructura modular, puntos de entrada y convenciones.
- Localiza los casos de uso, modelos, contratos y consumidores afectados.
- Examina gestión de errores, autenticación, persistencia y pruebas existentes.
- Comprueba si el cambio afecta a contratos públicos, datos persistidos o transacciones.
- Limita las modificaciones al ámbito de la tarea.
Para sistemas heredados, consulta además arquitectura-legacy.md. Para decisiones estructurales importantes, consulta 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.
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.
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 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.
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
tenantIdenviado 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 y 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.