Skip to content

Estructura de la Base de Datos (Relacional)

La base de datos es la parte más compleja e importante de toda aplicación, dado que la misma puede volverse a construir, pero los datos una vez perdidos, perdidos quedan. Por tal motivo, la mayor parte del tiempo de un proyecto debe emplearse en el diseño de la base de datos.

Para solventar el riesgo potencial de un pesimo diseño, se optó por implementar una serie de principios, los cuales son:

Normalización de la Base de Datos

“Es un proceso que sirve para organizar las tablas y evitar la redundancia o complejidad en los datos.”

Hay algunas reglas en la normalización de una base de datos. Cada regla se llama “forma normal”.

Si se cumple la primera regla, se dice que la base de datos está en “primera forma normal”. “Si se observan las tres primeras reglas, se considera que la base de datos está en “tercera forma normal”. Aunque son posibles otros niveles de normalización, la tercera forma normal se considera el nivel más alto para la mayoría de las aplicaciones.

Primera forma normal

  1. Eliminar los grupos repetidos de las tablas.
  2. Crear una tabla independiente para cada conjunto de datos relacionados.
  3. Identificar cada conjunto de datos con una clave principal.

No se debe usar varios campos para almacenar datos similares en una tabla. En caso de que esto ocurra, obligará a ir añadiendo campos para los datos que se van generando, lo cual no se adapta a un número dinámico de datos.

En su lugar, se debe colocar los datos en una tabla independiente y después vincularla mediante la clave foránea y primaria.

Segunda forma normal

  1. Crear tablas independientes para conjuntos de valores que se apliquen a varios registros.
  2. Relacionar estas tablas con una clave externa.

Los registros no deben depender de nada más que de la clave principal de una tabla (una clave compuesta, si es necesario).

Tercera forma normal

  1. Eliminar los campos que no dependen de la clave.

Los valores de un registro que no forman parte de su clave, no pertenecen a la tabla. En general, siempre que el contenido de un grupo de campos pueda aplicarse a más de un único registro, se recomienda crear una tabla independiente y añadir estos campos.

Si se aplica de forma erronea, vamos a generar un montón de tablas pequeñas con pocos registros, lo que hará que nuestra aplicación tenga un pesimo rendimiento.

Motor Utilizado

Existen una gran variedad de motores relacionales, algunos más populares que otros y cada uno con sus pros y contras respectivamente. Uno de estos motores es SQLite, conocido por ser ligero, por su simpleza y su confiabilidad. A diferencia de otros motores de bases de datos, los cuales requieren una configuración compleja y un servidor dedicado, SQLite es un motor de base de datos embebido que se integra directamente en la aplicación.

Normalmente, se suele creer que solo sirve para aplicaciones que funcionan en local. No obstante, eso no es cierto, ya que puede ejecutarse en cualquier dispositivo, y debido a su increible experiencia de desarrollo y su confiabilidad, se estan haciendo forks que permitan llevarlo a ser una base de datos distribuida que sea funcional para el entorno web moderno.

En concreto, el servicio que se esta usando es Turso, que no usa SQLite clasico como tal sino su variante LibSQL que mantiene la compatibilidad y que sus nuevas funciones estan escritas en Rust y no en C.

Funciones Empleadas

  1. Columnas Generadas: También llamadas “columnas calculadas” son columnas de una tabla cuyos valores son los de otras columnas en la misma fila (registro). Las columnas generadas se pueden leer, pero sus valores no se pueden escribir directamente. La única forma de cambiar el valor de este tipo de columna es modificar los valores de las columnas utilizadas para su definición. Ir a referencia para mayor información.

  2. Transacciones: Es una unidad única de trabajo. Si una transacción tiene éxito, todas las operaciones realizadas durante la transacción se confirman y se convierten en parte de la base de datos. Si en una transacción algo falla se cancela y se revierte todo. Ir a referencia para conocer más.

  3. Sentencias Preparadas: Son una característica de muchos sistemas de gestión de bases de datos que permiten ejecutar consultas SQL de manera más eficiente y segura. Se utilizan para prevenir ataques de inyección SQL y mejorar el rendimiento al permitir que el mismo comando SQL se ejecute múltiples veces con diferentes parámetros. Ir a referencia para conocer más.

  4. json_group_array(): Es una función agregada de SQLite que devuelve un array de valores.

  5. json_object(): Función agregada de SQLite que devuelve un objecto json de los valores proporcionados. Se uso en conjunto a la anterior función para el envió de los datos al cliente de una forma más manejable.

  6. coalesce(): Función agregada que devuelve el primer argumento que no sea null. Se uso para el momento de actualizar un campo.

ORM

El uso de un ORM (Object Relational Mapping) en cualquier proyecto de software ofrece múltiples ventajas frente a solo escribir SQL. Sin embargo, hay que tener en cuenta que no todos los ORM son iguales, cambian según el lenguaje que se este usando y sobretodo se debe tener en cuenta que cada uno posee caracteristicas únicas que deben ser analizadas.

Las ventajas y desventajas que se tuvieron en cuenta a la hora de decidir si utilizar un ORM o irse con SQL puro, fueron:

Ventajas

  1. Mejora en la productividad, nos permite escribir toda la capa de persistencia en el mismo lenguaje del backend.
  2. Reduce el código repetitivo, ya que maneja tareas comúnes y repetivas, como generar consultas, el mapeo de resultados y la gestión de transacciones. Esto nos permite enfocarnos en la lógica del negocio.
  3. Contribuye con la seguridad al prevenir ataques de inyección SQL.
  4. Permite la rápida adopción de cambios al momento de alterar la estructura de la base de datos.

Desventajas

  1. Curva de aprendizaje, se debe aprender tanto SQL y la sintaxis del ORM al tiempo.
  2. Limitantes a la hora de hacer consultas complejas o con necesidad de rendimiento 100% optimo.
  3. Dependencia al ORM, si el proyecto no posee una infraestructura a la altura, en caso de querer migrar a otro ORM la tarea se hará un infierno.

Nuestra elección

Luego de citar las ventajas y desventajas del uso de un ORM, se optó por utilizar dicha herramienta, exactamente el elegido fue Drizzle, el cual es un ORM de TypeScript que se enfoca en brindar un enfoque medio, las razones que nos llevaron a implementarlo son:

  1. La sintaxis se asemeja mucho a SQL, lo que disminuye la curva de aprendizaje y al tiempo usar el aprendizaje adquirido con SQL. Tanto que su filosofía es:

“si conoces SQL, conoces Drizzle ORM”.

  1. Genera una sola consulta SQL lo que es bueno porque reduce la cantidad de interacciones con la base de datos.
  2. La declaración de esquemas específicos para cada motor.
  3. Es una herramienta ligera y de alto rendimiento, siendo al día de hoy de las opciones con mejor rendimiento en el mercado.
  4. Compatibilidad con PostgreSQL, MySQL y SQLite.
  5. Adaptador específico para cada servicio, en nuestro caso Turso.

Schema de la Base de Datos

La base de datos esta constituida por 17 tablas, las cuales aplican los conceptos anteriores, buscando tener la mejor estructura posible; al momento de diseñar la estructura se tomo en cuenta la unicidad de los datos, el rendimiento y la confiabilidad del sistema. El esquema se muestra en :