lunes, 12 de agosto de 2013

No borres nada de la base de datos... pero ni se te ocurra usar borrados lógicos.

Me encanta la cara que me ponen los programadores y analistas cuando les suelto esta frase. Se quedan quietos pensando en la frase e intentando comprender como puede no ser una paradoja imposible de cumplir lo que les acabo de soltar en tan pocas palabras.

La primera parte de la frase se entiende fácil y rápidamente. Unos de los requisitos del sistema es no perder la información original que se ha borrado o modificado para poder consultarla en un momento futuro. En el caso de las modificaciones o borrados permitidos, la solución mas sencilla que viene a la cabeza es una solución completamente válida, aceptable y con unos problemas inherentes asumibles: Tablas de auditoría. Con ellas podemos recuperar fácilmente los valores de una entidad en una fecha concreta.


Cuando llegamos al caso de los borrados que no deben "perderse" la cosa se vuelve peliaguda y la solución que se viene a la cabeza tiene unas consecuencias cataclísmicas totalmente inaceptables: Los "borrados lógicos".

El problema con los "borrados lógicos es que raramente un borrado es un evento simple y atómico en un sistema con información relacionada. Esto significa que o bien tenemos corrupción de datos por su perdida de integridad o bien tenemos que escribir un buen montón de código que se encargue de gestionar el "borrado lógico"  ("borrados" en cascada, consultas de datos "borrados", no "borrados" o ambos a la vez, etc) y aun así, seguimos teniendo un problema con las claves principales y claves externas puesto que tendríamos que incluir la columna "IsDeleted" como parte de la clave principal de todas las tablas para evitar las colisiones de elementos borrados y no borrados que podrían ocurrir por la naturaleza de su clave primaria. Y no, identidades automáticas (autonuméricos por ejemplo) para todo no es la soluciona puesto que tendríamos que volver a tener todavía mas código de control en el caso de que no deban existir entidades con los mismos valores; ademas de que tenemos que controlar la posibilidad de que una entidad "borrada" se intentase dar de alta de nuevo. ¿Le borramos el campo "IsDeleted"? Eso significa 2 modos de dar de alta una entidad en nuestro sistema (Insert y Update), más caos, excepciones a controlar y hacks en el software del sistema. ¿Damos de alta una nueva entidad? Perderíamos las referencias a las relaciones de esa entidad con los datos con los que estuviera relacionado antes de la eliminación de este en el sistema y la trazabilidad de una entidad no quedaría completa o tendríamos que introducir mas hacks para reasignar todas la referencias de la antigua entidad a la nueva.

 Total, que terminamos con un sistema caótico, inmantenible y lleno de hacks debido a la miriada de excepciones a la regla general que hay que implementar para gestionar todas las posibilidades y ya ni siquiera podemos confiar en las reglas de integridad del motor de base de datos puesto que nos las hemos cargado con los borrados "logicos" y los autonumericos en todas las tablas.

La más mínima modificación del esquema de los datos o las reglas de negocio es un dolor tremendo en la rabadilla por la cantidad de cosas que hay que tocar para volver a dejar el sistema estable. Se olvidarán casos muy excepcionales que explotarán en producción en el momento más delicado. Se introducirán nuevo bugs y reaparecerán bugs que ya se habían corregido. Estos bugs en producción dejaran la base de datos en un estado incoherente que habrá que arreglar a mano con muchas prisas y con el alto riesgo que supone tocar en producción a mano.

 Terminamos con un ejercito de desarrolladores apagando fuegos constantemente sin realizar ninguna tarea productiva más que mantener al cliente conforme. La antítesis de la productividad.

Cuando tienes un martillo todo parecen clavos.


El problema es que, como desarrolladores, tenemos el punto de vista de la tecnología con la que trabajamos. En este caso, al estar trabajando con un motor de bases de datos racionales, tenemos en la cabeza 4 conceptos (CRUD) con los cuales queremos resolver todos los requisitos que nos presenten. Y si no nos dejan hacer Delete hacemos Update.

Digamos que el departamento de marketing nos dice que quiere borrar un elemento de su catálogo de ventas. ¿Deberían todas las ordenes de compra anteriores al borrado de ese elemento desaparecer en cascada? ¿Y las facturas de esas ordenes de compra también? Por supuesto que no. Entonces ¿Estoy equivocado? ¿Necesitamos "borrados lógicos"? Por un lado no queremos dejar la BD en un estado inconsistente teniendo facturas que imprimar ordenes de compra inexistentes; pero por otro lado nuestros usuarios nos han pedido que lo borremos. ¿O no lo han hecho?

Pues resulta que no. En vez de aceptar "borrar" como un Delete de base de datos indagamos el porqué los usuarios borran entidades y qué están intentando hacer en realidad.

Lo que quieren decir con "borrar" es que el producto debe ser descontinuado. No se quiere vender más ese producto. No se quieren recibir más órdenes de compra de ese producto. El producto no debe aparecer en el catálogo de ventas para los clientes.

Así que debemos proveer una representación explícita de esa tarea tanto en el modelo de datos como, por supuesto, en la interfaz del usuario. No más interfaces CRUD de sacar un grid con las líneas y pulsar el botón borrar.

Este patrón se repite a lo largo de todo el proceso del negocio de un sistema.

Los pedidos no se borran. Son cancelados.
Los empleado no son borrados. Son despedidos o retirados.
Los puestos de trabajo no son borrados. Son ocupados o su necesidad revocada.
Etc.

Esto es lo que debemos modelar. Basta ya de oscuras y bizarras soluciones "invisibles" al negocio del sistema. Nuestro software de negocio y repositorios de persistencia debe ser consciente de los estados de las entidades puesto que estos estados son parte del negocio.

Regla de oro: Si una entidad, por requisito del sistema, no debe ser borrada (Delete) entonces debe existir uno o más estados en el modelo del negocio que represente la discontinuidad de esa entidad en el momento actual en el tiempo.

Si se tiene el requisito de no poder eliminar una entidad de persistencia y en las historias del usuario no aparecen estados que representen ese momento de discontinuidad de la entidad tenemos un problema. Pero el problema no es técnico ni de estrategias de manejo de datos. El problema es el proceso de negocio del cliente. Podrían realizar su trabajo con lápiz y papel y aun así, debido a un proceso mal planificado, se encontrarían con situaciones de incoherencia de información e integridad de datos en su papeleo.


2 comentarios:

  1. Qué extraña sensación de deja-vu... ¬_¬U

    ResponderEliminar
  2. Amén.

    He tenido que trabajar en proyectos que contenían borrados lógicos y sólo funcionan con entidades sencillas. El problema es que no suele ser un requisito para una entidad concreta sino para un sistema completo, al final los DAO terminan con unos 'cachoprocedimientos' que pugnan por cubrir todas las posibilidades...

    ResponderEliminar