6.9 KiB
Arquitectura del código compartido entre frontend y backend
Referencia especializada del skill
rdx-solid. Consulta arquitectura.md y, según la tarea, arquitectura-backend.md y arquitectura-frontend.md.
1. Propósito
Compartir contratos y lógica realmente reutilizable sin acoplar el navegador a implementaciones internas del servidor. Compartir código no equivale a compartir todas las entidades, dependencias o políticas del sistema.
2. Qué puede compartirse
| Candidato | Condiciones |
|---|---|
| DTO y contratos públicos | Deben describir el intercambio entre aplicaciones, no tablas internas. |
| Tipos y enumeraciones contractuales | Semántica común y política de compatibilidad explícita. |
| Esquemas de transporte | Independientes de secretos y recursos de servidor. |
| Funciones puras | Sin dependencias del entorno ni efectos secundarios. |
| Formatos e identificadores interoperables | Responsabilidad compartida, validación adecuada. |
| Clientes generados de API | Fuente de verdad definida y versión compatible. |
Evita compartir directamente repositorios, servicios de dominio internos, modelos ORM, credenciales, acceso a archivos de servidor, lógica de autorización exclusivamente backend o tipos que dependan de Node.js cuando se ejecutarán en el navegador.
3. Diseñar contratos explícitos
Un contrato de API es una representación de intercambio. No tiene por qué coincidir con la entidad de dominio, el modelo de persistencia ni el modelo de presentación.
export interface OrderResponse {
id: string;
status: 'pending' | 'confirmed';
totalMinorUnits: number;
currency: string;
}
Un OrderResponse puede ser compartido entre aplicaciones; la entidad Order del backend puede mantener invariantes y objetos de valor internos diferentes. Evita filtrar propiedades privadas o campos de base de datos al contrato público.
4. Validación y límites de confianza
- TypeScript comprueba tipos durante el desarrollo, pero no valida datos externos en ejecución.
- Valida datos que cruzan límites de confianza, especialmente en el servidor.
- Puedes compartir esquemas de intercambio cuando ahorre duplicación y sean compatibles con ambos entornos.
- No consideres que la validación de formulario sustituye la validación del servidor.
- Diferencia validación de transporte de reglas de negocio y autorización.
// Ejemplo conceptual con una biblioteca de esquemas ya adoptada.
export const createOrderSchema = schema.object({
customerId: schema.string(),
items: schema.array(
schema.object({
productId: schema.string(),
quantity: schema.number().int().positive(),
}),
),
});
export type CreateOrderInput = Infer<typeof createOrderSchema>;
schema e Infer representan APIs ilustrativas; utiliza las de la biblioteca real del proyecto. No añadas una dependencia nueva exclusivamente por este ejemplo.
5. Estructura de paquetes
packages/
contracts/
src/
orders/
order-response.ts
create-order-request.ts
index.ts
shared-utils/
src/
format-identifier.ts
apps/
api/
web/
Este esquema es opcional. Un proyecto pequeño puede compartir tipos sin múltiples paquetes; un monorepo grande puede requerir separación adicional. No crees paquetes vacíos ni abstraigas antes de conocer los consumidores.
6. Dirección de dependencias
- El paquete compartido no debe importar código de
apps/apini deapps/web. - Frontend y backend pueden depender de contratos compartidos, sin dependencia recíproca entre sus implementaciones.
- Evita ciclos y dependencias transitivas que incluyan recursos exclusivos de Node.js en bundles de navegador.
- Usa puntos de entrada públicos y exportaciones explícitas cuando aporten límites estables.
- Separa código específico de entorno cuando sea necesario; no confíes en condiciones de ejecución que permitan empaquetar secretos.
apps/web ────┐
├──► packages/contracts
apps/api ────┘
7. Versionado y compatibilidad
- Trata los contratos compartidos como contratos públicos cuando los consumidores se despliegan por separado.
- Considera cambios aditivos, campos opcionales y estrategias de transición compatibles.
- Evita cambiar el significado de un campo sin una migración explícita.
- Define versiones y política de publicación cuando exista distribución independiente.
- No asumas que actualizar una dependencia compartida actualiza automáticamente a todos sus consumidores.
- Considera pruebas de contrato o compatibilidad entre versiones cuando el riesgo lo justifique.
8. Generación frente a mantenimiento manual
Puedes generar tipos y clientes desde OpenAPI, JSON Schema u otra fuente contractual si reduce divergencias. Debe existir una fuente de verdad identificable.
- No edites manualmente archivos generados.
- Evita mantener simultáneamente tipos manuales y generados con significado idéntico sin necesidad.
- Comprueba la estabilidad del generador, su coste y la calidad del resultado.
- No introduzcas generación de código para dos tipos sencillos que evolucionan juntos sin problemas.
9. Reutilización y propiedad
Antes de mover código a un paquete compartido:
- Identifica consumidores reales.
- Comprueba que representa el mismo conocimiento o contrato.
- Verifica compatibilidad de entorno y dependencias.
- Define quién mantiene el contrato.
- Comprueba que el movimiento no hace más difíciles cambios locales.
No confundas duplicación accidental con dos reglas similares pertenecientes a ámbitos de negocio diferentes.
10. Riesgos habituales
- Exponer entidades ORM al frontend.
- Empaquetar módulos de Node.js en el bundle del navegador.
- Compartir lógica de autorización que depende de contexto de servidor y utilizarla como control de seguridad en cliente.
- Cambiar contratos sin verificar consumidores desplegados.
- Crear
commonosharedcomo contenedor de responsabilidades inconexas. - Generar importaciones circulares mediante archivos barril.
- Acoplar pruebas de frontend a implementaciones internas de backend.
11. Verificación y lista de comprobación
- ¿El código compartido expresa un contrato o comportamiento realmente común?
- ¿No importa implementaciones específicas de frontend o backend?
- ¿Es compatible con los entornos donde se ejecutará?
- ¿La validación de datos externos está situada en límites correctos?
- ¿Se han preservado contratos públicos y compatibilidad de versiones?
- ¿La fuente de verdad del esquema está definida?
- ¿No se han exportado secretos ni detalles internos?
- ¿Se han comprobado builds, tipos y pruebas de ambos consumidores afectados?
Principio final: comparte contratos y conocimiento estable, no el acoplamiento entre implementaciones.