Es muy frecuente al desarrollar aplicaciones que los requisitos no estén completamente definidos desde el principio y/o que éstos sufran cambios a corto y medio plazo, bien porque son descartados, modificados o porque aparecen otros nuevos. Es por ésto que las metodologías ágiles establecen estrategias que se adaptan a dichos precarios escenarios.
Carlos Ble en su post Diseño emergente, también para la base de datos describe una forma de trabajar a nivel de base de datos mediante un enfoque emergente. Si bien muchos programadores llevan utilizando estrategias ágiles (o emergentes) desde hace mucho tiempo, y que lo que Carlos describe también se realiza desde hace mucho tiempo; la clarividencia con la que técnicas concretas (Scrum, Kanban, …) y Carlos en su artículo ponen de manifiesto un determinado aspecto es, a mi juicio, muy interesante y revelador, porque te hace pensar de forma consciente sobre problemas que llevas resolviendo, “inconscientemente”, desde hace mucho tiempo.
Así, con independencia de lo correcto o no de cada solución, del tipo de contexto que cada uno tengamos que resolver (y que serán diferentes), el compartir nuestras estrategias hace que cada cual piense en sus propios problemas y tome de cada uno aquello que le resulta útil.
Por tanto, te propongo un ejemplo práctico de cómo normalmente yo aplico el enfoque de Diseño emergente en la base de datos.
Breve reseña al diseño emergente
Cuando alguien diseña algo (un avión, una casa, un coche, …) es habitual planificar todos y cada uno de los aspectos que finalmente formarán parte del producto. Así, todo es diseñado y planificado de antemano. A modo de ejemplo llevado al extremo, podemos imaginar un sencillo y humilde tornillo ubicado en la parte interna de uno de los motores de los ya obsoletos transbordadores espaciales de la NASA. Es muy probable que dicho tornillo esté presente explícitamente en multitud de planos (no en todos, claro, en algunos estará presente de forma implícita) y que su tamaño, dimensiones y composición química fueran diseñados o elegidos realizando un cálculo concreto. Quien montara el motor, puso ese tornillo y no otro, porque así estaba decidido de antemano.
Un carpintero, puede construir una silla a partir de planos similares a los usados en la NASA, pero será probable que no se le indique el tipo concreto de tachuela que debe usar ni cuantas debe poner. Así, cuando el carpintero tenga que poner las tachuelas a la silla, decidirá en ese mismo momento que tipo de tachuela pondrá y con que separación. Es decir, el diseño final de la silla ha emergido del propio proceso de construcción, en oposición a una cuidadosa planificación previa.
Llevado al extremo, podríamos imaginar a un artista frente a un tronco de madera; el artista no sabe (no ha decidido aún) que figura va a esculpir, empieza observando el material, va esquilmando los trozos de madera agrietados o dañados, golpeando aquí y allá. Al poco vislumbra la imagen de un caballo rampante y comienza a dar grosera forma al material, pero aún no sabe cual será la posición de las patas, la cola, la crin, … todos y cada uno de los golpes del artista son espontáneos no hay absolutamente ningún tipo de diseño previo, cuando termina su obra, podemos decir que el caballo rampante ha emergido del proceso realizado por el artista.
En el diseño de software y concretamente en el agilismo, el diseño emergente se produce al aplazar decisiones, análisis, trabajos y/o requisitos sin que ello impida comenzar a desarrollar y utilizar las aplicaciones y sistemas involucrados. En cierto momento, surge la necesidad de implementar un requisito que ni siquiera se había comentados con anterioridad, este nuevo requisito a emergido del uso y/o desarrollo de la aplicación.
¿Es necesario el diseño emergente en la base de datos?
El artículo de Carlos me gustó porque puso de manifiesto un aspecto que ha estado presente desde siempre, pero que él ha sabido exponer de forma explícita y no como “un problema menor” con el que nos encontramos a diario. En los siguientes párrafos, intentaré transmitir el tipo de problema que se trata.
Como en cualquier elemento de software, un cambio en una parte afecta a otras y las bases de datos (concretamente relacionales) no son una excepción. Por ejemplo el añadir, quitar o cambiar las propiedades de un campo de una tabla, requiere normalmente ajustar otras partes del sistema. En casos concretos se dispone de herramientas que automatizan ciertos procesos (eg. CRUD), pero en general, cualquier cambio aparentemente nimio, exige un esfuerzo apreciable en otras partes del sistema.
El uso de ORM (Object relational mapping) es hoy muy común y permite automatizar buena parte del trabajo que hace un DBA (DataBase Administrator) tiene bastantes (y ágiles) ventajas. Algunos de los problemas que voy a describir se mitigan o eliminan completamente usando un ORM pero otros se mantienen igualmente. A mi en particular sólo me gusta usar ORM en aplicaciones compactas que hacen uso intensivo de un framework (eg. un portal web), en general, yo me siento muy cómodo teniendo en todo momento control sobre la base de datos, por lo que asumiré en lo siguiente que no se usa un ORM.
Respecto de las bases de datos, algunos ejemplos habituales que me vienen a la cabeza son los “campos zombies”, que serían aquellos que se introducen en el diseño pero que luego nunca se usan o se usan realmente poco, están en la base de datos, pero como si no estuvieran, de forma similar tendríamos las “tablas zombies”; otro ejemplo podrían ser las “tablas tocapelotas” que en diseño se han definido como relaciones “uno a cero o uno” pero cuya partición termina siendo un infierno porque realmente debería ser una única tabla y en todas las consultas se arrastra un horripilante “LEFT OUTER JOIN” que nunca o casi nunca actúa (siempre se hace el JOIN); otros muchos ejemplos provienen de un exceso o falta de normalización, a veces el diseño está muy normalizado penalizando en demasía el rendimiento obligando o bien a desnormalizar el diseño o a crear estructuras adicionales de cacheado; el caso contrario es una falta de normalización (cuando en diseño se piensa que el producto cartesiano en la práctica será pequeño, pero no es así) que produce tablas con ingentes cantidades de registros (innecesarios).
En definitiva, existen muchas situaciones en las que el mejor diseño no está claro (los motivos de que no estén claros son muy variados y pocas veces debidas a cuestiones técnicas) y es sólo cuando la aplicación se pone en marcha que emergen esos fallos dejando patente un mal diseño.
Diseño emergente en la base de datos
Como solución a los problemas mencionados, Carlos propone incluir en las tablas que lo requieran un campo en el que serializar todos aquellos datos cuyo diseño no esté claro aún. Por ejemplo, podemos llamar al campo xdata y serializar a json, xml, … después utiliza esa información en la capa de aplicación.
Yo utilizo ese esquema cuando los datos dentro de xdata no tengo que procesarlos internamente en la base de datos, habitualmente porque hay aplicaciones externas y personalizables que hacen uso de ella. Aun así, rara vez directamente en un campo xdata como tal, yo prefiero normalizar ese campo, veamos como.
La tabla de registro
Muchos conoceréis el “Windows Registry”, realmente no es mas que una gran tabla que asigna un valor a una clave. La estructura arbórea que surge, proviene únicamente de fijar delimitadores en las claves (como una ruta de archivo en /home/josejuan/datos/listado.txt).
Un resumen muy general de mi estrategia preferida sería precisamente esa, tener una tabla que almacena pares de “clave + valor”.
Por poner un ejemplo, los diferentes estados en los que puede estar un semáforo, se podrían codificar como:
De esta forma, no es preciso crear una tabla semáforo para almacenar los posibles estados en que puede estar. Si por ejemplo cuestionamos la necesidad de tener un campo “telephone 3” en la tabla “USER” se podría codificar en la misma tabla anterior como:
Por claridad y rendimiento, suelo tener tablas de registro especiales para conjuntos de datos especiales. Por ejemplo, si nuestro sistema cuenta con entidades “EMPRESA” será habitual tener una tabla de registro especial para esta entidad.
Me consta que este tipo de estrategias de una u otra forma es usada por muchos programadores.
Ejemplo práctico
Por escribir algo de código y clarificar la estrategia anterior, supongamos que estamos creando una aplicación (usando Extreme programming) y que por el momento únicamente necesitamos registrar cuentas de acceso para los usuarios, en tal caso, con la siguiente tabla sería suficiente:
El Product Owner rápidamente empieza a pedir requisitos que si bien están claros, son coherentes y son validables, tenemos serias dudas de su utilidad práctica final (algo subjetivo mientras no se demuestre lo contrario) ¿serán necesarios los cuatro campos de teléfono, dos de fax, el correo principal, el secundario y otros tantos campos “de imperiosa necesidad”?. Tras ver las orejas al lobo, decidimos cumplir con los requisitos usando una tabla de registro, que podría ser ésta:
Por tema de eficiencia, hemos dejado dos campos valor: uno pequeño y con datos en la página de registro y otro blob de acceso lento. Como verás, el campo pequeño es de tipo varbinary porque será capaz de almacenar cualquier tipo de dato que sea necesario (nosotros almacenamos un número como un número, no como una cadena, una fecha como una fecha, etc…).
Para probar el rendimiento de las consultas, vamos a generar 10.000 registros de usuario con aproximadamente unos 100.000 registros de valores en la tabla de registro. Esto se puede hacer con el siguiente script:
Los datos han generado para cada usuario la siguiente información:
last_access, un campo fecha con la fecha de último acceso del usuario.
error_count, un campo numérico con el número de errores asociados al usuario.
message_status, un campo de texto con un mensaje de estado asociado al usuario.
message list, que es una tabla asociada al usuario con un histórico de mensajes (mensaje 1, mensaje 2, …).
Para acceder a los datos, se puede utilizar una vista que simule una tabla, de forma que se minimizan los cambios a realizar en las consultas (básicamente añadir o quitar campos) en caso de cambios en la estructura. Por ejemplo la siguiente vista permite acceder (indizada adecuadamente, es decir, es eficiente) a los campos de registro de cada usuario:
(Esta consulta es estándar pero puede implementarse mediante PIVOT TABLE que en ciertos escenarios puede tener un mejor rendimiento).
Para acceder al histórico de mensajes, también podemos simular una tabla a partir de la de registro, por ejemplo:
En ambos casos, a partir de una tabla de registro a la que pueden añadirse estructuras adicionales, hemos creado dos “tablas virtuales” sin tener que modificar la estructura de la base de datos.
Por ejemplo, para listar los usuarios con más de 9 errores en los últimos 3 días bastaría escribir:
O bien para saber cual es el número medio de mensajes y longitud media por mensaje de todos los usuarios bastaría con hacer:
Como vemos, podríamos incluso realizar vistas y consultas sobre datos (claves) que ni siquiera existen.
Utilidad de la tabla de registro
La utilidad de la tabla de registro es bastante limitada como medio de evolucionar “emergentemente” una aplicación. En mi opinión, otras técnicas ágiles, usar ORM o refactorizar serán probablemente mejores alternativas que trabajar en el diseño y estructura de la aplicación apoyándose en la tabla de registro.
Además, aunque depende del tipo de aplicación, cambios en la lógica de negocio e interface de usuario son mucho más costosos que en la base de datos, un DBA experimentado no tiene problemas en añadir, eliminar o modificar objetos de la base de datos, quizás son algo molestos, sobre todo si lo usas en combinación con una herramienta CASE y datos en vivo, pero nada que no se pueda hacer en unos minutos.
Sin embargo, la tabla de registro es insustituible como medio para añadir de forma rápida y versátil estructuras que no suponen un coste añadido o a posteriori. Para almacenar valores de informe, de histórico, datos constantes, de configuración, … y en general, datos que no tendrán un coste o cobertura muy alta en la aplicación (si un dato es altamente actualizado, accedido, mapeado, … desde muchos puntos, será preferible que tenga “entidad propia”).
Así, yo la uso indiscriminadamente como forma de disponer de un gestor No-SQL dentro de mi gestor Sí-SQL.
Vía | Diseño emergente, también para la base de datos.
Más información | Emergent Design.
Más información | Windows Registry.
Más información | Extreme programming.
Más información | Object relational mapping. Database Administrator.