148 lines
6.9 KiB
Markdown
148 lines
6.9 KiB
Markdown
# Arquitectura del código compartido entre frontend y backend
|
|
|
|
> Referencia especializada del skill `rdx-solid`. Consulta [arquitectura.md](arquitectura.md) y, según la tarea, [arquitectura-backend.md](arquitectura-backend.md) y [arquitectura-frontend.md](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.
|
|
|
|
```typescript
|
|
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.
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```text
|
|
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/api` ni de `apps/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.
|
|
|
|
```text
|
|
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:
|
|
|
|
1. Identifica consumidores reales.
|
|
2. Comprueba que representa el mismo conocimiento o contrato.
|
|
3. Verifica compatibilidad de entorno y dependencias.
|
|
4. Define quién mantiene el contrato.
|
|
5. 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 `common` o `shared` como 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.
|