Compartir esta publicación

Share on facebook
Share on linkedin
Share on twitter
Share on email

Tabla de contenidos

El uso de ORMs (mapas de datos) viene con un gran poder pero también con una gran responsabilidad. Los ORMs nos dan una forma cómoda de manejar las bases de datos evitando algunos boilerplate; también pueden ser utilizados de forma incorrecta, siendo esto una fuente de problemas en el rendimiento o la escalabilidad.

ORM

El mapeo de asociaciones de muchos a muchos o many-to-many relationships es una de esas opciones comúnmente utilizadas y que puede dar lugar a más perjuicios que beneficios.

Empecemos con un ejemplo sencillo donde hay 2 entidades: Productos y Etiquetas. Los productos pueden tener múltiples etiquetas y las etiquetas pueden tener múltiples productos. Intentemos usar el mapeo de relaciones muchos a muchos en ambas entidades. Simplemente invocando product.tags es sencillo recuperar una lista de las etiquetas para el producto dado; con tag.products podemos obtener fácilmente todos los productos de una etiqueta. Bastante directo y sencillo de utilizar.

Pero cada vez que solicitamos un producto, se pueden ejecutar varias consultas extra bajo el capó, lo que puede llevar a un mal rendimiento.
Además, ¿necesitamos toda la información de las etiquetas cada vez que pedimos la información del producto?

Claro que podemos habilitar el lazy loading (añadiendo configuración extra a nuestras entidades) para evitar pedir los datos innecesarios pero cuando los datos son necesarios, los ORMs suelen consultar la base de datos individualmente para cada «hijo» (el problema conocido como N+1 query problem). Al final, mucho más sencillo para el codificador, pero peor para la CPU y los IOPS.

Además, el mapeo de many-to-many relationships es bastante restrictivo en términos de información extra. ¿Qué pasa si necesitamos saber cuándo se asignó una etiqueta a un producto, o quién hizo la asignación? En este caso está bastante claro que necesitamos una relación intermedia donde se guarden estos datos.

orm design1Extraído de la documentación (conocido ORM para PHP):

¿Por qué las many-to-many relationships son menos comunes? Porque frecuentemente se quiere asociar atributos adicionales a una asociación, en cuyo caso se introduce una clase de asociación. En consecuencia, la asociación directa de muchos a muchos desaparece y se sustituye por asociaciones de uno a muchos/de hombre a uno entre las 3 clases participantes.

De todos modos, vamos a suponer que no se necesitan datos adicionales, sólo la relación. Ambas entidades deben existir de forma independiente entre sí, es decir, cada entidad forma parte de una raíz agregada diferente. Y, desde la perspectiva de DDD, las raíces de los agregados no deberían hacer referencia a otro agregado, sólo a su ID.

Además, este modelo puede tener problemas en un entorno concurrente. En un escenario real, podríamos  cumplir con los siguientes casos de uso:

  1. La descripción del producto 1 se actualiza
  2. Se actualiza el nombre de una etiqueta
  3. Se asigna/elimina la etiqueta del producto 1

Estas 3 operaciones deberían hacerse al mismo tiempo sin ningún problema. Pero al utilizar el mapeo de relaciones muchos a muchos, el tercer caso de uso puede necesitar ser bloqueado hasta que el resto se complete o pueden producirse algunas inconsistencias.

Una vez más, para evitar este conflicto deberíamos dividir nuestro modelo en 3 participantes.orm design2De cualquier manera, ha surgido un nuevo concepto importante en nuestro dominio: El etiquetado es una nueva raíz agregada con la responsabilidad de asignar etiquetas a los productos. Con esta solución se ha solucionado el problema de escribir en las 3 tablas simultáneamente.

Pero ya no puedo hacer product.tags…

Eso es cierto, pero la verdadera pregunta es: ¿por qué necesitamos navegar entre dos raíces agregadas en absoluto?

Pensando en términos de CQRS, en el lado de la consulta, cuando necesitamos recuperar información normalmente se puede hacer con una consulta específica uniendo todas las tablas necesarias. De esta forma evitando hidratar entidades y ahorrando memoria y tiempo de CPU se puede devolver un simple DTO con los datos solicitados.

Ejemplo: obtener la lista de etiquetas de un id de producto dado

Val product = productRepository.findById(productId)
Val tags = product.tags()
Foreach (Tags as tag) {
	tagsDTO [] = TagDTO.from(tag)
}
Return tagsDTO

Vs:

Seleccionar * de Producto p
INNER JOIN etiquetas g ON g.product_id = p.id
INNER JOIN etiquetas t ON g.tag_id = t.id

En el lado de los comandos, debemos modificar sólo un agregado al mismo tiempo para que no haya necesidad de navegar de un agregado a otro.

Usando esta versión de presupuesto de CQRS también estamos rompiendo responsabilidades y teniendo un código mucho más sencillo.

Más ventajas

  • Evitar toda la configuración de carga perezosa y las consultas pre-cached que pueden traer algunos problemas de escalabilidad (problemas de consulta N+1).
  • La lógica del dominio debe ser gestionada por nosotros en nuestro dominio, no por el ORM que forma parte de la infraestructura.
  • Nuestro modelo podría ser mapeado en tres bases de datos diferentes sin cambiar ninguna línea de código. Este es un punto importante para la escalabilidad.
  • Los 3 modelos podrían pertenecer a diferentes contextos acotados porque están desacoplados dándonos un mejor rendimiento.
  • Hibernate no recomienda su uso tampoco.  

Conclusión

Si somos capaces de NO mapear las relaciones entre las raíces de los agregados, podemos crear muchos «islotes» independientes (los modelos de dominio) que no dependen unos de otros, pudiendo así distribuirse de forma independiente para conseguir la máxima escalabilidad, sin depender de ninguna tecnología de mapeo subyacente entre ellos.

Piensa en otra solución cuando el many to many sea una opción posible para tu problema.

Este artículo ha sido escrito por Oriol Saludes (FullStack Developer en Apiumhub) & Christian Ciceri (Software Architect en Apiumhub).

No utilizar mapeos de asociación exóticos:

Los casos de prueba prácticos para asociaciones reales de muchos a muchos son raros. La mayoría de las veces se necesita información adicional almacenada en la «tabla de enlaces». En este caso, es mucho mejor utilizar dos asociaciones uno-a-muchos a una clase de enlace intermedia. De hecho, la mayoría de las asociaciones son de uno a muchos y de muchos a uno. Por esta razón, debe proceder con cautela cuando utilice cualquier otro estilo de asociación.

Conversion Post ES

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Suscríbete a nuestro boletín de noticias

Recibe actualizaciones de los últimos descubrimientos tecnológicos

Acerca de Apiumhub

Apiumhub reúne a una comunidad de desarrolladores y arquitectos de software para ayudarte a transformar tu idea en un producto potente y escalable. Nuestro Tech Hub se especializa en Arquitectura de Software, Desarrollo Web & Desarrollo de Aplicaciones Móviles. Aquí compartimos con usted consejos de la industria & mejores prácticas, basadas en nuestra experiencia.

Posts populares
PDF gratuito Software Interview Series

¿Tienes un proyecto desafiante?

Podemos trabajar juntos