Nota: Me apetecía escribir este post en inglés pero me surgió la siguiente duda.. ¿cuántos de los visitantes hispano parlantes dejarían de leer el post por estar en inglés? Si lees este post y te resuta de ayuda estaría bien que añadieses un comentario diciendo si lo hubieses leído en inglés tambien. Así puedo tener feedback sobre si escribir más en castellano o con mi inglés chungo.
Aunque las bases de datos No-Sql están a la vanguardia, muchos seguimos usando también Sql y seguirá siendo así porque Sql y No-Sql tienen ventajas y desventajas. Sin embargo habiendo trabajado con ambos paradigmas, me he llevado cosas que he aprendido con No-Sql a Sql y la ventaja es muy “agil” 🙂
Normalmente las bases de datos Sql se diseñan normalizadas, es decir, se evita la duplicidad de datos mediante tablas que se relacionan entre sí con claves foráneas y demás restricciones. Unas tablas apuntan a otras mediante los identificadores y estas cosas. La ventaja de la normalización y de las restricciones tipo “unique”, etc, es que no se repiten datos y por tanto el motor optimiza el acceso a ellos. Además podemos mantener una cierta integridad de los datos evitando que se inserten datos sin sentido.
La desventaja es que los cambios estructurales en la bd cuestan muchísimo. Hay herramientas de migración automática que funcionan bastante bien cuando los cambios son relativamente sencillos pero hay migraciones muy complicadas.
Ahora bien… ¿con qué frecuencia hay que hacer cambios en la bd cuando estamos en pleno desarrollo? Pues en cada iteración. Cuando la funcionalidad es nueva, practicamente cada dia o cada semana.
¿Y cuánto nos cuesta manejar en el código un diseño de base de datos complejo? Es caro, porque la optimización se paga con tiempo de desarrollo. Una base de datos es compleja porque tiene relaciones.
Cargar con relaciones entre tablas desde el inicio es muy caro. Diseñar una bd normalizada desde el inicio me parece tremendamente ambicioso, demasiado optimista. Me supone crear capas de anticorrupción para que la lógica de negocio no se vea afectada por la extrema complejidad del esquema de datos. Me supone mucho tiempo en las migraciones.
Personalmente prefiero crear bases de datos los más planas posibles, que me permitan
desarrollar muy rápido y hacer cambios sin problemas de migraciones. Una vez que el feedback de los usuarios me dice que ya conozco suficiente del negocio, de verdad, me planteo ir normalizando la bd para optimizar.
Para esto suelo crear un campo en la tabla de tipo texto, donde puedo guardar serializada toda la información que voy necesitando, usando un formato clave-valor. Luego en código cuando cargo los datos me encargo de deserializar e interpretar esos datos. Así cuando necesito quitar o poner un campo no tengo que tocar la estructura de la base de datos. Ejemplo:
Quiero gestionar las tareas de un usuario. Un usuario debe tener un nombre unico, algunos datos de contacto y puede tener muchas tareas. Cada tarea tiene una serie de datos, de momento titulo y descripcion.
Esquema de BD inicial:
Estructura Tabla user:
field | type |
---|---|
id | Pk, autoincremental… |
username | varchar(50) |
fields | text |
userdata | text |
Ejemplo de una fila rellena en la tabla:
id | username | fields | userdata |
---|---|---|---|
0 | fulanito | email=fulanito@gmail.com#age=20#phone=30 | {tasks: [{‘title’: ‘un titulo’, ‘description’: ‘probando’}]} |
En el campo “fields” de la tabla voy poniendo lo que creo que son campos de esta tabla, separados por el simbolo #. Si necesito poner o quitar campos o necesito que unos objetos tengan un campo y otros no, no tengo que tocar la BD.
Para gestionar este tipo de campos tengo una clase que sabe leerlos y transformarlos en un diccionario por ejemplo y luegos los “getters” de mi objeto de negocio los puedo implementar consultando a ese parser. No importa que use algun tipo de ORM, normalmente admiten métodos en los objetos de negocio.
En el campo “userData” he metido informacion en formato “json” (por poner un ejemplo de formato) que me permite guardar cualquier cosa.
La desventaja de esto es que cuando haya varios usuarios, habra datos duplicados. En la versión normalizada tendriamos una tabla User con varios campos, una tabla Task. Si la relacion es de muchos a muchos, ademas necesitamos una tabla UserTasks que una ambas. La otra gran desventaja es que si tengo que hacer ciertas consultas, las tendre que hacer en código en vez de usar SQL.
La ventaja es que puedo desplegar nuevas versiones sin preocuparme por las migraciones estructurales de base de datos. Puedo retrasar la normalización de la base de datos hasta que de verdad conozca lo suficiente del negocio. De esta manera consigo el esquema más simple posible que me permite programar con comodidad y que a la vez está optimizado. No tengo que cargar con sobreingeniería.
Si normalizo primero, igual decido crear la tabla UserTasks para que exista una relación de muchos a muchos. Si mas adelante las estadisticas de uso de nuestra aplicación y el conocimiento que tenemos de nuestros usuarios nos dicen que una tarea no la pueden tener dos usuarios, entonces hemos sobrediseñado.
Tenemos una tabla que no sirve. El problema es el coste que tiene mantener un código que hace frente a un requisito innecesario.
A menudo se piensa en el coste que tiene escribir una funcionalidad. Pero muy pocas veces se mide el coste que tiene mantener código que no se usa. Y en mi experiencia es un coste altísimo que te hace ir lento. Es una bola de nieve.
Para ir del esquema desnormalizado al normalizado, la prioridad es ir creando campos en las tablas para aquellos atributos por los que quiero poder hacer consultas. Por ejemplo, si nunca hay necesidad de buscar por el numero de telefono, este campo no tengo por qué extraerlo a un campo de bd. Incluso puede que nunca.
Por otra parte, para argumentar sobre el rendimiento de la BD, como con muchas otras cosas, conviene tener datos empíricos objetivos. Es decir, no voy a trabajar en un problema de rendimiento hasta que no haya hecho pruebas de estres o de carga y tenga unos datos que vemos que no son buenos y que hay que mejorar.
A menudo, optimización y rendimiento son enemigos de mantenibilidad. Por eso en mi opinión y mi experiencia (a base de equivocarme) sólo hay que optimizar cuando hay algo que ya funciona bien.
Esta forma de evolucionar las bases de datos puede suponer para algunos hacer las cosas al revés. Lo que ya les pasa con algunas prácticas como TDD. Sin embargo cuanto más experiencia tengo más pienso justo lo contrario, que he pasado demasiado tiempo haciendo las cosas al revés.
Hay que poner en perspectiva lo que uno aprende en la universidad porque a veces son conceptos de hace muchos años, que ahora tienen que ser revisados de otra manera.