metis-desk/.agents/skills/rdx-solid/referencias/arquitectura-backend.md

9.4 KiB

Arquitectura backend

Referencia especializada del skill rdx-solid. Consulta primero 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. 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 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 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.