MyBatis vs JPA: no existe la tecnología mala.

«¿Qué se supone que es este proyecto?»

El proyecto de mi empresa actual es lo que podríamos llamar un «frankenstack». MyBatis y JPA conviven dentro del mismo proyecto. El problema era que los «creadores», es decir, los miembros iniciales que escribieron este código, ya se habían marchado todos de la empresa.

Los que nos quedamos, el equipo actual, estábamos desconcertados. No había ni un solo documento que explicara con qué criterio se habían mezclado estas tecnologías. Así que la situación solo fue empeorando.

Al final, dentro de un mismo servicio, unos se metían en XML para implementar funciones parecidas mientras otros diseñaban entidades. Se había convertido en una extraña forma de «anarquía técnica». El proyecto se transformaba poco a poco en un monstruo cada vez más difícil de mantener, hasta que finalmente se convocó una reunión.

«Unifiquemos todo bajo una sola pila tecnológica.»

Mezclar tecnologías sin criterios no es libertad, es solo confusión.

«¿No es JPA demasiado lento para usarlo?»

El ambiente en la reunión estaba tenso. En especial, quienes ya estaban acostumbrados a MyBatis se oponían con fuerza a unificar todo en JPA. Su argumento principal era la velocidad.

«¿No recuerdan aquella página de estadísticas que hicimos con JPA y que tardaba 20 segundos solo en cargar? Si escribimos el SQL directamente, sale en un segundo. Es arriesgado depender de una caja negra que no controlamos del todo.»

Era cierto que algunas lógicas tardaban decenas de segundos solo en cargar datos. Pero yo estaba convencido de que no era un problema de la tecnología, sino de la experiencia de quien la usa. Abrí el portátil allí mismo y saqué el famoso «código de 20 segundos».

El código era desastroso. Consultaba una List<Entity>, pero cada vez que el bucle tocaba un objeto relacionado, golpeaba la base de datos una vez más. Era el clásico problema N+1.

«Esto no significa que JPA sea lento. Significa que nosotros obligamos a JPA a trabajar de forma ineficiente. Miren.»

Apliqué un Fetch Join en ese mismo momento y reduje la consulta a un solo golpe. Después del despliegue, el tiempo de carga pasó de 20 segundos a 1 segundo. Fue el instante en que vi cambiar la expresión de mis compañeros.

Antes de culpar a la herramienta, conviene leer el manual.

Un hallazgo inesperado: no era un engendro, sino un híbrido

Una vez aclarado el malentendido con JPA, ¿bastaba con unificarlo todo en JPA? Cuando lo analizamos a fondo, vimos que esa tampoco era la respuesta correcta.

Intentar resolver con JPA consultas estadísticas complejas o descargas de Excel con cientos de miles de filas implicaba un coste demasiado alto de mapeo de objetos, y las consultas dinámicas también eran incómodas de escribir. En cambio, MyBatis era mucho más intuitivo y rápido para ese tipo de trabajo. Entonces me pregunté: «¿Cómo resuelven esto otras empresas?» Frustrado, pasé la noche buscando en Google y revisando blogs técnicos.

Para mi sorpresa, muchas empresas tecnológicas no insistían en usar solo JPA. Algunas usaban QueryDSL para consultas complejas de alto rendimiento y otras, igual que nosotros, combinaban MyBatis con JPA.

En ese momento todo encajó. «Entonces quienes se fueron no mezclaron estas tecnologías sin pensar.»

Ya conocían las ventajas y limitaciones de cada herramienta y habían separado intencionalmente las responsabilidades por tecnología. El verdadero problema fue que se marcharon sin documentarlo, así que quienes heredamos el sistema terminamos discutiendo por qué no lo habían unificado.

Más allá de los límites de JPA: QueryDSL y Projection

Entonces surgió otra pregunta: ¿podíamos retirar MyBatis y resolver este problema usando solo tecnologías del ecosistema JPA? Ahí es donde entran QueryDSL y Projection.

// 1. Interfaz que define solo los datos necesarios (sin necesidad de DTO)
public interface DailyStat {
    String getDate();
    Long getTotalSales();
}

// 2. Consultar con QueryDSL o un JPA Repository
// Solo se SELECTionan esas dos columnas de la BD, asi que es tan rapido como MyBatis.
List<DailyStat> stats = repository.findDailySales();

Cuando entendí estas tecnologías, dejó de tener sentido volver al infierno de XML de MyBatis. Dentro del propio ecosistema JPA también era posible escribir consultas de alto rendimiento.

Consejo práctico: dejen de pelear y háganlas convivir

Al final, nuestro equipo eligió una «convivencia con principios» en lugar de una «unificación absoluta». (Y a largo plazo, nuestro objetivo era movernos hacia QueryDSL.)

Lo interesante es que esta estrategia de supervivencia que elegimos, «usar JPA para escribir y una herramienta especializada para leer», desde la perspectiva de arquitectura no es otra cosa que una forma muy básica del patrón CQRS(Command and Query Responsibility Segregation).

No hace falta dividir la base de datos de forma aparatosa. Con solo adoptar en el código la filosofía de separar las responsabilidades de comandos y consultas, el proyecto se vuelve mucho más limpio.

Para cerrar: la tecnología no tiene la culpa

A menudo discutimos diciendo: «JPA es lo mejor» o «No, en el trabajo real MyBatis manda». Pero lo que aprendí de esta experiencia es que no existe la tecnología mala. Solo existen situaciones en las que una tecnología se usa para el trabajo equivocado.

JPA es como una varita mágica, pero si lanzas mal el hechizo, explota. MyBatis es como un martillo robusto, pero puede hacerte ver clavos en todas partes.

Si ahora mismo tu equipo está sufriendo por culpa de código legado o por diferencias de preferencia tecnológica, detente un momento. Tal vez dentro de ese caos haya razonamientos profundos de quienes estuvieron antes. Encontrarlos y convertirlos en reglas claras, así es como un equipo de desarrollo se vuelve realmente sólido.

Deja un comentario