feat: agregar evaluación de alternativas a Sequelize en Metis Desk
This commit is contained in:
parent
d8a09888e8
commit
ae9c490be5
323
docs/evaluacion-de-alternativas-a-sequelize.md
Normal file
323
docs/evaluacion-de-alternativas-a-sequelize.md
Normal file
@ -0,0 +1,323 @@
|
||||
# Evaluación de alternativas a Sequelize en Metis Desk
|
||||
|
||||
Fecha: 10 de octubre de 2026. Estado: estudio técnico; no se ha cambiado el ORM, instalado
|
||||
ninguna alternativa ni modificado la base de datos. Las conclusiones de encaje y esfuerzo
|
||||
son una valoración del código actual, no resultados de una migración o benchmark.
|
||||
|
||||
## 1. Recomendación
|
||||
|
||||
No recomiendo una sustitución completa inmediata. Recomiendo una prueba comparativa
|
||||
acotada de **TypeORM y MikroORM**, manteniendo MariaDB, las tablas, el dominio y los
|
||||
contratos HTTP. Si la prioridad es controlar SQL y reforzar su tipado, incluir **Kysely**
|
||||
en la prueba, reconociendo que es un constructor de consultas y no un ORM completo.
|
||||
|
||||
- **TypeORM:** camino más próximo al funcionamiento actual, con dialecto MariaDB explícito.
|
||||
- **MikroORM:** candidato interesante para persistencia de relaciones y seguimiento de
|
||||
cambios; su beneficio debe compensar el contexto por petición y los modelos separados.
|
||||
- **Kysely:** alternativa muy alineada con los repositorios que ya utilizan SQL explícito.
|
||||
- **Prisma 7:** buen candidato para ergonomía de CRUD, condicionado por la transición de
|
||||
versiones y el uso intensivo de SQL/bloqueos del proyecto. Prisma 8 no entra en la prueba
|
||||
con MariaDB mientras su documentación indique ausencia de MySQL.
|
||||
- **Drizzle:** atractivo para TypeScript y esquema relacional, pero no lo elegiría aquí sin
|
||||
validar primero compatibilidad MariaDB en consultas y migraciones.
|
||||
- **Sequelize 6:** mantenerlo y mejorar aspectos concretos sigue siendo una opción válida.
|
||||
|
||||
La decisión debe salir de medir menos código de persistencia, mejores tipos y mantenimiento,
|
||||
sin perder garantías de concurrencia; no de elegir el ORM con ejemplos de CRUD más breves.
|
||||
|
||||
## 2. Situación real del proyecto
|
||||
|
||||
La inspección del repositorio muestra:
|
||||
|
||||
| Aspecto | Estado observado | Consecuencia |
|
||||
| --- | --- | --- |
|
||||
| ORM y motor | Sequelize 6.37.8; driver mariadb 3.5.4; contenedor MariaDB 11.8 | Comparar contra esta combinación, no contra PostgreSQL ni un ejemplo SQLite. |
|
||||
| Acoplamiento | 90 ficheros de servidor importan o mencionan Sequelize; 24 implementaciones de repositorio tienen nombre `sequelize-*.repository.ts` | El cambio afecta a bastantes piezas aunque la frontera arquitectónica esté bien definida. Los números son de código fuente, no de tablas. |
|
||||
| Capas | Puertos en Application; modelos ORM y mappers en Infrastructure; dominio DDD propio | La mayoría de entidades, Value Objects y casos de uso deberían conservarse. |
|
||||
| Persistencia | Combinación de Model, consultas SQL parametrizadas, helpers relacionales y transacciones | Una sustitución no consiste solo en regenerar entidades. |
|
||||
| Concurrencia | `FOR UPDATE`, bloqueos del catálogo y orden fijo de adquisición | Deben reproducirse dentro de la misma transacción/conexión. |
|
||||
| Históricos | Revisiones de activos y asignaciones, entre otros | Deben mantenerse identidad, orden e inmutabilidad del histórico. |
|
||||
| Sesiones | `connect-session-sequelize` 8.0.6 sobre `http_sessions` | Eliminar Sequelize completamente requiere sustituir o adaptar el store de Express. |
|
||||
| Normalización | Activos tienen persistencia relacional; todavía aparecen JSON/JSON_EXTRACT en otros ámbitos | Cambiar ORM no completa la normalización. Son proyectos distintos. |
|
||||
|
||||
Fuentes locales principales: `apps/server/src/infrastructure/http/identity-runtime.ts`,
|
||||
`modules/assets/src/api/infrastructure/persistence/sequelize-asset.repository.ts`,
|
||||
`sequelize-brand.repository.ts`, `asset-inventory-relational.ts`,
|
||||
`modules/companies/src/api/infrastructure/persistence/sequelize-dashboard.repository.ts`,
|
||||
`modules/resources/src/api/infrastructure/persistence/resource-catalog.model.ts` y
|
||||
`docs/arquitectura/capas-del-servidor.md`.
|
||||
|
||||
El dominio sigue utilizando `UniqueID`, `Result`, `Maybe` y mappers. Ningún ORM debería
|
||||
sustituir esos contratos ni generar nuevos UUID con otra estrategia por defecto.
|
||||
|
||||
## 3. Comparación resumida
|
||||
|
||||
Las valoraciones de esfuerzo son relativas a Metis Desk y abarcan reescritura y validación,
|
||||
no solo conexión inicial. No son métricas objetivas de calidad universal.
|
||||
|
||||
| Alternativa | MariaDB | Ventaja principal aquí | Coste de adaptación | Valoración |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Sequelize 6 | Dialecto actual ya operativo | Continuidad y menor riesgo | Bajo si se mantiene | Referencia contra la que medir el beneficio. |
|
||||
| TypeORM | Soporte explícito | Modelo/repository/query builder reconocibles | Medio | Primera prueba si se busca migrar con menor distancia conceptual. |
|
||||
| MikroORM | Driver explícito | Unit of Work, relaciones y seguimiento de cambios | Medio-alto | Primera prueba si se busca mejorar mantenibilidad de persistencia compleja. |
|
||||
| Prisma 7 | Conector MySQL admite MariaDB | Cliente generado y CRUD tipado | Medio-alto | Condicionado por versiones y consultas específicas. |
|
||||
| Prisma 8 | Documentación consultada: sin MySQL | No aplicable al motor actual | No evaluable aquí | Excluido de la propuesta actual. |
|
||||
| Drizzle | Integración documentada MySQL/mysql2; soporte específico MariaDB pendiente de verificar | Esquemas TypeScript y consultas próximas a SQL | Medio; riesgo adicional de dialecto | No primera elección con MariaDB. |
|
||||
| Kysely | Dialecto MySQL/mysql2; comprobar diferencias MariaDB | Consultas SQL compuestas y tipadas | Medio | Buena alternativa cuando importa más SQL que gestión automática de objetos. |
|
||||
|
||||
## 4. Sequelize: mantener o actualizar
|
||||
|
||||
**Ventajas:** funciona con el motor y las sesiones actuales; los errores y transacciones ya
|
||||
están integrados; dispone de inferencia de atributos TypeScript, que el proyecto utiliza.
|
||||
Su documentación reconoce que determinadas declaraciones de modelo siguen requiriendo
|
||||
trabajo manual. [TypeScript en Sequelize 6](https://sequelize.org/docs/v6/other-topics/typescript/).
|
||||
|
||||
**Inconvenientes en este proyecto:** se repiten definiciones entre modelos y tipos;
|
||||
`database.query<T>` declara un tipo de resultado, pero no demuestra que el SQL devuelva
|
||||
esas columnas; las migraciones y helpers necesitan disciplina propia.
|
||||
|
||||
**Mejoras sin migración:** separar el ejecutor de SQL de consultas concretas, reutilizar
|
||||
mappers reales y revisar transacciones/lecturas excesivas. No crear un repositorio genérico
|
||||
que oculte reglas de negocio o bloqueos. No atribuir a Sequelize costes de consultas que
|
||||
cargan todo un contexto de inventario: cambiar el ORM no evita esa decisión de diseño.
|
||||
|
||||
**Actualización a v7:** es un camino distinto de cambiar de ORM. La documentación consultada
|
||||
sigue etiquetándola como alpha; no la tomaría como actualización automática del proyecto.
|
||||
[Estado de Sequelize 7](https://sequelize.org/docs/v7/).
|
||||
|
||||
## 5. TypeORM
|
||||
|
||||
### Capacidades verificadas
|
||||
|
||||
Tiene soporte explícito MySQL/MariaDB, configuración `type: "mariadb"` y driver mysql2.
|
||||
Permite definir entidades mediante `EntitySchema`, sin imponer decoradores. Incluye
|
||||
migraciones, query builder y transacciones; las operaciones transaccionales deben usar el
|
||||
manager proporcionado por la transacción.
|
||||
[MariaDB](https://typeorm.io/docs/drivers/mysql/),
|
||||
[EntitySchema](https://typeorm.io/docs/entity/separating-entity-definition/),
|
||||
[transacciones](https://typeorm.io/docs/transactions/),
|
||||
[migraciones](https://typeorm.io/docs/migrations/setup/).
|
||||
|
||||
### Ventajas para Metis Desk
|
||||
|
||||
- Sustitución relativamente directa de modelos y repositorios de infraestructura.
|
||||
- Permite conservar SQL particular donde aporte claridad.
|
||||
- Es posible mantener entidades de persistencia separadas del dominio DDD.
|
||||
- Su dialecto MariaDB reduce incertidumbre respecto a una integración solo MySQL.
|
||||
|
||||
### Inconvenientes y adaptación
|
||||
|
||||
Seguirán existiendo modelos de persistencia y mappers; no desaparece toda la duplicación.
|
||||
Los resultados de consultas SQL crudas tampoco quedan automáticamente validados.
|
||||
Las cascadas, relaciones y métodos de guardado deben configurarse expresamente para no
|
||||
modificar históricos o borrar registros relacionados por accidente.
|
||||
|
||||
Reescribiríamos modelos, repositorios, composición de módulos, conexión y migraciones;
|
||||
revisaríamos traducción de errores de FK/unique y semántica de upsert. Evitaríamos Active
|
||||
Record dentro del dominio y `synchronize` como sustituto de migraciones. La documentación
|
||||
actual anuncia TypeORM 1.0: fijaríamos una versión concreta y sus instrucciones, sin mezclar
|
||||
ejemplos antiguos de 0.2/0.3 con la API elegida.
|
||||
|
||||
**Valoración:** el reemplazo convencional más cercano al código actual. La ganancia principal
|
||||
sería ergonomía y consistencia, no una transformación radical de la arquitectura.
|
||||
|
||||
## 6. MikroORM
|
||||
|
||||
### Capacidades verificadas
|
||||
|
||||
Dispone de driver MariaDB, Unit of Work e Identity Map. Puede calcular los cambios de
|
||||
entidades gestionadas al hacer `flush`; ofrece migraciones y generación de entidades desde
|
||||
el esquema. La definición `defineEntity` permite inferir tipos sin decoradores.
|
||||
[Arquitectura y drivers](https://mikro-orm.io/docs/architecture),
|
||||
[Unit of Work](https://mikro-orm.io/docs/unit-of-work),
|
||||
[defineEntity](https://mikro-orm.io/docs/define-entity),
|
||||
[generador](https://mikro-orm.io/docs/entity-generator),
|
||||
[migraciones](https://mikro-orm.io/docs/migrations).
|
||||
|
||||
### Ventajas para Metis Desk
|
||||
|
||||
- Interesante para persistir conjuntos relacionados sin repetir tantas operaciones de guardado.
|
||||
- El seguimiento de cambios puede ayudar a actualizar únicamente entidades modificadas.
|
||||
- Herramientas para definir, generar y evolucionar el esquema.
|
||||
- Buen candidato para relaciones complejas si se mantiene clara la separación entre dominio
|
||||
y representación persistida.
|
||||
|
||||
### Inconvenientes y adaptación
|
||||
|
||||
Requiere comprender el ciclo de vida de las entidades gestionadas y sus colecciones.
|
||||
La Identity Map debe aislarse por petición, mediante fork/contexto; un EntityManager global
|
||||
compartido sin aislamiento no es aceptable.
|
||||
[Contexto por petición](https://mikro-orm.io/docs/identity-map).
|
||||
|
||||
Las entidades DDD actuales no se convertirían directamente en entidades gestionadas:
|
||||
se mantendrían mappers. Si un mapper crea una representación persistida completamente
|
||||
nueva en cada guardado, parte de las ventajas del seguimiento de cambios se reduce.
|
||||
La prueba debe resolver este punto, no suponer que Unit of Work elimina todos los mappers.
|
||||
|
||||
Reescribiríamos modelos/repositorios y añadiríamos contexto por petición, respetando límites
|
||||
transaccionales. Las revisiones continuarían como registros explícitos; una cascada de
|
||||
borrado no sustituye las reglas del histórico. Los bloqueos existentes seguirían siendo
|
||||
necesarios.
|
||||
|
||||
**Valoración:** candidato con ventajas potenciales de persistencia más profundas, a cambio de
|
||||
mayor aprendizaje e integración que TypeORM. No introducir sus abstracciones en Domain.
|
||||
|
||||
## 7. Prisma: distinguir versiones
|
||||
|
||||
### Capacidades y situación verificadas
|
||||
|
||||
Prisma 7 documenta MariaDB mediante el conector MySQL y el adaptador mariadb. Ofrece
|
||||
introspección, cliente generado, transacciones interactivas y baseline para adoptar una BD
|
||||
existente sin recrearla.
|
||||
[Conector v7](https://www.prisma.io/docs/orm/v7/core-concepts/supported-databases/mysql),
|
||||
[transacciones v7](https://www.prisma.io/docs/orm/v7/prisma-client/queries/transactions),
|
||||
[baseline v7](https://www.prisma.io/docs/orm/v7/prisma-migrate/workflows/baselining).
|
||||
|
||||
La documentación principal ya presenta Prisma 8 y el repositorio publica versiones RC.
|
||||
La página actual de transacciones indica que Prisma 8 no tiene soporte MySQL. No se debe
|
||||
suponer que instalar `latest` conserva compatibilidad MariaDB ni trasladar ejemplos v7 a v8.
|
||||
[Releases](https://github.com/prisma/orm/releases),
|
||||
[documentación actual de transacciones](https://www.prisma.io/docs/orm/fundamentals/transactions).
|
||||
|
||||
### Ventajas de evaluar v7
|
||||
|
||||
- Buen autocompletado y selección tipada de campos en el cliente generado.
|
||||
- CRUD y relaciones sencillas pueden expresarse de forma compacta.
|
||||
- Introspección útil para comenzar a partir de nuestras tablas.
|
||||
- Esquema declarativo e historial de migraciones formalizado.
|
||||
|
||||
### Inconvenientes y adaptación
|
||||
|
||||
Añade un esquema propio y un paso de generación al monorepo. Los modelos generados no
|
||||
reemplazan entidades DDD ni DTOs Zod. Las consultas particulares del dashboard y el orden
|
||||
de bloqueos del inventario requerirían revisión y, donde proceda, SQL parametrizado dentro
|
||||
de la misma transacción; no basta con sustituirlas por escrituras anidadas de CRUD.
|
||||
|
||||
La introspección no genera reglas de negocio ni reconstruye las migraciones históricas de
|
||||
Sequelize. Hay que revisar tipos físicos, restricciones e índices y preservar aquellos
|
||||
elementos no representados por el esquema. Existe además incertidumbre de evolución de
|
||||
versiones que debe resolverse antes de elegirlo para un motor MariaDB a largo plazo.
|
||||
|
||||
**Valoración:** atractivo para aplicaciones CRUD, pero no el candidato principal ahora.
|
||||
Prisma 7 podría evaluarse fijando versión; Prisma 8 queda fuera por el motor actual.
|
||||
|
||||
## 8. Drizzle
|
||||
|
||||
### Capacidades verificadas
|
||||
|
||||
Declara el esquema en TypeScript y ofrece consultas cercanas a SQL. La integración oficial
|
||||
consultada es MySQL con mysql2; Drizzle Kit puede introspectar una base existente.
|
||||
[MySQL](https://orm.drizzle.team/docs/mysql/get-started-mysql),
|
||||
[introspección](https://orm.drizzle.team/docs/get-started/mysql-existing).
|
||||
|
||||
Hay una petición abierta para comportamiento específico MariaDB y reportes de incompatibilidad
|
||||
con consultas relacionales basadas en LATERAL. Es evidencia de un área que requiere
|
||||
validación, no una prueba de que cualquier consulta Drizzle falle en MariaDB ni de que el
|
||||
problema afecte a todas las versiones/APIs.
|
||||
[Issue del proyecto](https://github.com/drizzle-team/drizzle-orm/issues/2007).
|
||||
|
||||
### Ventajas e inconvenientes para Metis Desk
|
||||
|
||||
Su enfoque resulta afín a una BD normalizada y a infraestructura que ya usa SQL. Evita
|
||||
imponer entidades gestionadas y decoradores. No elimina mappers ni reglas de guardado de
|
||||
agregados y no aporta Unit of Work al estilo MikroORM.
|
||||
|
||||
La dificultad extra aquí es verificar diferencias MariaDB/MySQL en generación de SQL,
|
||||
introspección, tipos y migraciones. Conservaría SQL explícito donde fuese necesario y no
|
||||
usaría una API relacional sin comprobar su SQL en MariaDB 11.8. La documentación consultada
|
||||
contiene ejemplos con paquetes RC: fijaríamos una versión y separaríamos APIs estables y RC.
|
||||
|
||||
**Valoración:** buena propuesta técnica si la prueba de compatibilidad es satisfactoria;
|
||||
menor prioridad que los candidatos con soporte MariaDB explícito.
|
||||
|
||||
## 9. Kysely, Knex y Objection.js
|
||||
|
||||
**Kysely no es un ORM de entidades.** Es un constructor SQL tipado; su dialecto MySQL usa
|
||||
mysql2. Sus tipos actúan en compilación y el driver determina los valores reales devueltos.
|
||||
[Introducción](https://kysely.dev/docs/intro),
|
||||
[drivers y tipos](https://kysely.dev/docs/getting-started).
|
||||
|
||||
Para Metis Desk, permite mantener el control sobre joins, proyecciones y escrituras de
|
||||
históricos, mejorando el tipado frente a SQL en strings. Su coste es seguir implementando
|
||||
mapeo, persistencia de relaciones y cambios de esquema; los tipos de tabla deben mantenerse
|
||||
alineados con la BD. Al emplear el dialecto MySQL, también hay que probar funciones y SQL
|
||||
específico de MariaDB. El problema de consultas relacionales automáticas de Drizzle no se
|
||||
traslada por sí solo a Kysely: son implementaciones distintas.
|
||||
|
||||
**Valoración:** potencialmente la opción más sencilla si lo que queremos sustituir es el
|
||||
uso de Sequelize como ejecutor SQL. Menos apropiada si buscamos guardado automático de
|
||||
objetos relacionados.
|
||||
|
||||
**Knex** cubre consultas/migraciones; **Objection.js** añade un ORM sobre Knex y deja las
|
||||
migraciones del esquema a esa capa. Podrían sustituir partes del proyecto, pero no los
|
||||
priorizaría sin una necesidad concreta frente a la prueba de Kysely/TypeORM/MikroORM.
|
||||
[Diseño de Objection.js y migraciones](https://github.com/Vincit/objection.js).
|
||||
Esta es una priorización por encaje, no una afirmación de falta de mantenimiento.
|
||||
|
||||
## 10. Riesgos comunes de la migración
|
||||
|
||||
1. **Transacciones:** no mezclar operaciones Sequelize y el nuevo cliente dentro de una
|
||||
operación que necesita atomicidad. Dos pools no comparten automáticamente una transacción.
|
||||
2. **Bloqueos:** preservar el orden catálogo → compañía → registros y comprobar la semántica
|
||||
de `FOR UPDATE`, especialmente al eliminar marcas mientras se crean activos.
|
||||
3. **Migrations:** durante coexistencia, mantener una única autoridad para cada tabla. No
|
||||
ejecutar dos generadores sobre las mismas tablas ni emplear sync/push para recrearlas.
|
||||
4. **Tipos físicos:** revisar UUID actuales, booleanos TINYINT, fechas sin hora, precisión de
|
||||
timestamps/decimales, nulabilidad y collation. No cambiar su representación por comodidad.
|
||||
5. **Errores:** conservar conflictos de unicidad/FK, códigos de aplicación y datos seguros.
|
||||
6. **Históricos:** preservar UUID, orden, fechas, usuario actor y referencias aunque un valor
|
||||
de catálogo se desactive.
|
||||
7. **Sesiones:** inicialmente puede seguir el store Sequelize. Para retirar la dependencia
|
||||
completamente habría que reemplazarlo preservando expiración, cookies y validación de
|
||||
sesiones, o asumir un cierre de sesiones controlado. No usar MemoryStore como reemplazo.
|
||||
8. **Normalización:** continuar la eliminación de JSON como trabajo separado, autorizado y
|
||||
con migraciones propias. Ninguna herramienta garantiza una BD normalizada por usarla.
|
||||
9. **Rendimiento:** medir número de consultas, tiempo y filas leídas con datos equivalentes;
|
||||
no afirmar que un ORM es más rápido solo por su API.
|
||||
|
||||
## 11. Prueba propuesta antes de decidir
|
||||
|
||||
No ejecutada en este informe. Usaría una copia de prueba de la BD, sin tocar desarrollo.
|
||||
|
||||
### Casos representativos
|
||||
|
||||
- **Marcas:** listar, crear, actualizar y rechazar borrado referenciado. Incluye bloqueos
|
||||
y restricciones, por lo que da más información que un CRUD de ejemplo.
|
||||
- **Un activo:** características normalizadas, plantilla con campos opcionales e histórico.
|
||||
- **Lectura de dashboard:** consultas con agregaciones y parámetros de compañía.
|
||||
- **Concurrencia:** dos solicitudes sobre el mismo catálogo/activo y fallo a mitad del guardado.
|
||||
|
||||
Los candidatos implementarían los mismos puertos. Primero reutilizaríamos los tests de
|
||||
contrato, dominio e integración existentes; después añadiríamos casos de concurrencia que
|
||||
falten. No sustituir MariaDB por SQLite para concluir compatibilidad.
|
||||
|
||||
### Criterios de aceptación
|
||||
|
||||
- Mismos datos, contratos HTTP, historial y reglas de borrado.
|
||||
- Sin cambios accidentales en DDL al adoptar el ORM.
|
||||
- Rollback completo y bloqueos equivalentes.
|
||||
- Menos declaraciones duplicadas o guardados manuales en un caso real.
|
||||
- Mejor detección de errores de columnas/resultados en TypeScript.
|
||||
- Consultas y tiempos comparables bajo el mismo volumen.
|
||||
- Inicialización, cierre de conexiones, migraciones y pruebas reproducibles con Node 24/PNPM.
|
||||
|
||||
Como estimación de planificación, dedicaría unas pocas jornadas a una prueba acotada y
|
||||
reservaría varias semanas para una migración completa y validación de los 24 repositorios
|
||||
nombrados y sus helpers. No es un presupuesto: el inventario detallado de tablas y el piloto
|
||||
son necesarios para convertirlo en una estimación fiable.
|
||||
|
||||
## 12. Ruta de adopción si la prueba resulta favorable
|
||||
|
||||
1. Fijar candidato/versión y actualizar las decisiones de arquitectura que hoy prescriben
|
||||
Sequelize; no empezar mezclando estilos sin una decisión explícita.
|
||||
2. Inventariar esquema y migraciones; crear baseline verificado con DDL y datos de prueba.
|
||||
3. Migrar un módulo o un conjunto transaccional completo, manteniendo los puertos y mappers.
|
||||
4. Repetir integración sobre MariaDB y comparar concurrencia/consultas.
|
||||
5. Ampliar por módulos; conservar Sequelize temporalmente para partes no migradas.
|
||||
6. Sustituir el store de sesiones y retirar la conexión/dependencia cuando no queden usos.
|
||||
7. Retirar la antigua ejecución de migraciones sin perder su historial ni trazabilidad.
|
||||
|
||||
Conclusión: **TypeORM** ofrece el recorrido más cercano; **MikroORM** debe demostrar mayor
|
||||
ahorro en persistencia compleja; **Kysely** puede ser la elección más coherente si preferimos
|
||||
SQL explícito. Mantener Sequelize sigue siendo preferible a una migración que solo cambie
|
||||
la sintaxis y no reduzca la complejidad real.
|
||||
Loading…
Reference in New Issue
Block a user