Publicado el 2021-04-25

English

El problema de la triple relación

En mis años trabajando como desarrollador de software he tenido que diseñar modelos de datos para una variedad de aplicaciones web. Considero que la integridad de datos y referencial es fundamental para desarrollar sistemas fiables. Si una combinación particular de datos de entrada debería ser tratada como inválida en mi aplicación, prefiero tener esa validación en la base de datos. Claramente, queremos esas mismas validaciones en el servidor de aplicación y/o en el cliente web por una cuestión de usabilidad, pero he encontrado que los datos inválidos van a hallar su camino hasta tu aplicación si no los validás en la base. Usualmente puedo cubrir nuestras necesidades con las herramientas provistas por cualquier RDBMS moderno (PostgreSQL en la mayoría de los casos) como claves foráneas, restricciones de unicidad (parciales o no), y CHECK CONSTRAINTS. Trato de evitar triggers si puedo, pero están ahí como última opción.

Sin embargo, he detectado un patrón en más de un proyecto que me cuesta implementar de forma de mantener la integridad referencial. Lo llamo el "problema de la triple relación".

Imaginen el siguiente modelo. Tres entidades todas relacionadas entre ellas: una entidad Organization, una entidad Department que pertenece a una Organization, y una entidad Employee que puede pertenecer a muchas Organizations y/o Departments. Queremos que los empleados se relacionen directamente con las organizaciones para poder moverlos entre departamentos sin que sean accidentalmente "eliminados" de la organización en sí.

La solución obvia

Este es un modelo simple, solamente necesitamos dos tablas adicionales para implementar las relaciones de muchos a muchos, y eso es básicamente todo. Pero hay una trampa: no podemos permitir que los empleados pertenezcan a departamentos de organizaciones de las que no forman parte.

Me he topado con este mismo patrón en tres ocasiones diferentes en proyectos desarrollados con Django. Quería una solución que no implicara triggers y que no dependiera (al menos enteramente) de claves foráneas compuestas, pues Django simplemente no las soporta.

Después de ponerle un poco de pienso, se volvió evidente que iba a requerir claves foráneas compuestas si quería que la base de datos realizara esta validación. Para circunvalar las limitaciones de Django, vamos a tener que crear esas claves foráneas en una migración y agregar un par de claves foráneas simples para proveer las mismas relaciones en una forma que Django las pueda ver.

Una solución mejorada, aunque más compleja

Esta solución hace tres cambios:

Con esto hacemos que sea imposible para un empleado unirse a un departamento si no pertenece a esa organización primero. De la misma forma, si quitamos a un empleado de una organización, la operación en la base fallará o bien eliminará todas las relaciones entre ese empleado y los departamentos de la organización, dependiendo de la acción que hayamos puesto en el ON DELETE.

Con todos nuestros requerimientos ahora satisfechos, debemos tener en cuenta que este es un modelo más complejo de entender y que una inserción en EmployeeDepartment ahora tiene que procesar 4 claves foráneas en lugar de 2, potencialmente afectando el rendimiento. Asegurate de que los beneficios de este enfoque superen sus desventajas.

¡Feliz modelado!