Escoger el modelo de datos para nuestra aplicación tiene un efecto profundo: no solo afecta cómo se desarrolla el software que estamos construyendo, sino también cómo se conceptualiza el problema a resolver. Al construir aplicaciones, es común trabajar con capas de abstracción que ocultan la complejidad de las capas anteriores al proveer un modelo de datos claro. Estas abstracciones permiten que distintos grupos de personas trabajen juntos de forma efectiva. Por lo tanto, es importante escoger el modelo de datos más adecuado para el sistema.
Tipos de modelos de datos
- Jerárquico (El modelo inicial) → Desarrollado en los 1950s por IBM. No se adaptaba bien a las relaciones many-to-many. Modelo Jerárquico
- Relacional (SQL) → Solución al modelo jerárquico. Poco flexible
- Documental → Datos que vienen en documentos autocontenidos (JSON). Poco soporte de relación entre documentos
- Modelo de grafos → Nodos y ejes
El Modelo Relacional
- Propuesto por Edgar Codd en 1970
- Los datos se organizan en relaciones (tablas) donde cada relación es una colección no ordenada de tuplas (filas)
- Casos de uso originales
- Procesamiento transaccional → operaciones de movimiento de dinero en un banco, reservas de aerolineas
- Procesamiento analítico en lotes → reportes, payroll, analítica
- Sigue el paradigma de schema-on-write (Esquema al escribir)
- El esquema de los datos es explicito y el motor de BD se asegura que todos los datos escritos a la BD sean consistentes con el esquema predefinido.
NoSQL
- Surgió con un hashtag de Twitter en el 2009
- def. Not ONLY SQL
- Sigue el paradigma de schema-on-read (Esquema al leer)
- Surgió debido a estos factores:
- Necesidad de mayor escalabilidad que las BDs relacionales.
- Preferencia por software open-source gratis envés de productos de BDs relacionales empresariales.
- Operaciones de consultas especializadas que no están bien soportadas por el modelo relacional.
- Frustración con las restricciones de los esquemas relacionales.
- Necesidad de un modelo más dinámico y expresivo
Problema: Objetos vs Relaciones → Desajuste por Impedancia (Impedance mismatch)
-
Impedance Mismatch: Problema de incongruencia entre el modelo relacional y los objetos en el código de la aplicación. Es el conjunto de dificultades técnicas y conceptuales que surgen cuando se almacenan objetos de programación en bases de datos con modelos relacionales
- Si los objetos en la app se almacenan en una BD con modelo relacional (SQL), se necesita una capa de traducción entre el código de la app y la modelo de la BD
-
Solución → Mapeo Objeto-Relacional (ORM: Object-relational mapping)
- Frameworks de ORM
- Reducen el código boilerplate requerido para esa capa de transición
- Ejemplos: Apache OpenJPA, SQLAlchemy, TypeORM
Expresiones en distintos Modelos
- Perfil de Linkedin
- Modelo Relacional
- Modelo Documental NoSQL (JSON)
{
"user_id": 251,
"first_name": "pedro",
"positions": [
{"job_title": "founder", "organization": "EmpresaA"},
{"job_title": "ceo", "organization": "EmpresaB"}
],
...
}
Con el modelo JSON, la estructura de árbol implícita en el perfil de Linkedin, se vuelve explícita en el modelo de datos
Versiones iniciales de una aplicación pueden encajar bien en un modelo documental JOIN-Free, pero los datos tienen una tendencia a volverse más interconectados a medida que surgen nuevas funcionalidades
- En algún punto va a necesitar many-to-many y por ende utilizar joins
- Ejemplo Linkedin → Representar la organización donde trabaja la persona como una entidad.
Debate: ¿Cómo representar de mejor forma las relaciones de las entidades en una base de datos?
Es una discusión más vieja que los esquemas NoSQL. Viene desde los primeros sistemas de bases de datos computacionales.
- Caso: La IMS de IBM utilizaba el modelo jerárquico. Este modelo no soportaba JOINs. En los 60s, los desarrolladores tenían que decidir entre duplicar datos (Normalizar) o resolver manualmente las referencias de un record a otro. IMS de IBM. Este es el mismo problema que tienen los desarrolladores hoy en día con las BDs documentales.
La solución a esto en su entonces fue el modelo relacional de SQL. El modelo relacional permite poner todos los datos al descubierto con una lista de tuplas (tabla). El optimizador de consultas en una BD relacional automáticamente decide que partes de la consulta ejecutar y en qué orden hacerlo.
Estas decisiones ya no las toma un desarrollador
- “Solo se construye un optimizador de consultas una vez. Todas las apps que utilicen la BD se benefician de el”
Las BDs documentales se devolvieron al modelo jerárquico → almacena records embebidos (many-to-one) en el mismo record, no en otra tabla
- Cuando se trata de relaciones many-to-many, el modelo documental y el modelo relacional no son muy distintos. En ambos casos, el ítem relacionado se referencia mediante un identificador único. Este identificador se utiliza como llave foránea en el otro ítem en el modelo relacional. En el modelo documental se usa como referencia a otro documento.
Modelo Relacional vs Modelo Documental
- Documental
- Flexibilidad de esquema
- Mejor localidad de datos → Capacidad para mover computo (objetos) a datos almacenados
- Poco soporte de JOINS
- Relacional
- Buen soporte de joins
- Buen soporte de relaciones many-to-one y many-to-many
- Poca flexibilidad de esquema
¿Convergencia entre el modelo relacional y documental?
- Postgres ya soporta documentos JSON como tipo de datos desde la versión 9. Documentación Postgres
- Hay drivers de Mongo que automáticamente resuelven las referencias externas de documentos. Resuelve el problema desde el lado del cliente.
Parece ser que las BDs relacionales y documentales se están volviendo cada vez más parecidas. Esto es algo bueno porque los modelos de datos se complementan entre sí. Si una BD soporta datos que son document-like y también soporta consultas relacionales, las aplicaciones pueden utilizar la combinación de ambas funcionalidades que mejor se ajuste a sus necesidades.
Regla de pulgar:
Use documentales cuando
- Tiene datos con relaciones one-to-many.
- Necesita que un árbol de datos embebidos se cargue en la aplicación desde el inicio.
- Estructura de datos tipo documento.
Use relacionales cuando
- Necesita muy buen soporte de joins
- Tiene relaciones many-to-many
Lenguajes de Consulta
-
Dos tipos de lenguajes: Declarativo e Imperativo
-
Declarativo → SQL
-
Le digo a la máquina que datos y transformaciones quiero realizar frente a los datos, pero no le digo como realizar la consulta
SELECT * FROM animals WHERE family = "sharks"
-
-
Imperativo → Lenguajes de programación (Java, python, JS)
-
Le digo a la máquina, como ejecutar la consulta
function getSharks(){ var sharks = []; for (var i=0; i<animals.length; i++){ if(animals[i].family === "sharks"){ sharks.push(animals[i]); } } return sharks; }
-
-
La misma consulta se puede expresar de forma genérica con algebra relacional: sharks = σ_family="Sharks" (animals)
SQL Provee mejor soporte para algunos tipos de consultas
Consultas tipo Map Reduce (Documentales) → Ej: Obtener número de tiburones por mes
SQL
SELECT date_trunc('month', observation_timestamp) AS observation_month, 1
sum(num_animals) AS total_animals
FROM observations
WHERE family = 'Sharks'
GROUP BY observation_month;
Documental (Mongo)
db.observations.mapReduce(
function map() { 2
var year = this.observationTimestamp.getFullYear();
var month = this.observationTimestamp.getMonth() + 1;
emit(year + "-" + month, this.numAnimals); 3
},
function reduce(key, values) { 4
return Array.sum(values); 5
},
{
query: { family: "Sharks" }, 1
out: "monthlySharkReport" 6
}
);
Modelo de Grafos
def. Base de datos que almacena los datos como relaciones entre nodos conectados por ejes
Ejemplo: Representar el matrimonio de dos personas
Estructura→ ejemplo Neo4J
- Cada vertice (Nodo) consiste de
- ID
- Ejes hacia afuera
- Ejes hacia adentro
- propiedades llave-valor
- Cada eje consiste de
- Id
- Vertice origen
- Vertice destino
- etiqueta
- Propiedades llave - valor
Los grafos son muy buenos para la evolucionabilidad del sistema. Un grafo se puede extender fácilmente para acomodar cambios en las estructuras de datos de la app.
Ejemplo de lenguaje para BDs de grafos → Cypher (Lenguaje de consultas utilizado en Neo4J)
Ejemplo: Insertar el hecho que Idaho queda en USA y que USA queda en América del norte. Insertar que Lucy nació en Idaho
CREATE
(NAmerica:Location {name: 'north america', type: 'continent'}),
(USA:Location {name:'united states', type: 'country'}),
(Idaho:Location {name: 'idaho', type 'state'}),
(Lucy: Person {name:'Lucy'}),
(Idaho) -[:WITHIN] -> (USA) -[:WITHIN] -> (NAmerica)
(Lucy) -[:BORN_IN] -> (Idaho)
Este modelo permite realizar consultas muy complejas con una sintaxis sencilla.
Ejemplo: Encuentre los nombres de las personas que emigraron de USA a Europa
MATCH
(person) -[:BORN_IN]-> () -[:WITHIN*0..]-> (us:Location {name:'United States'}),
(person) -[:LIVES_IN]-> () -[:WITHIN*0..]-> (eu:Location {name:'Europe'})
RETURN person.name
//[:WITHIN*0..] -> follow a within edge, zero or more times
Encuentra todos los vertices (personas) que
- Tengan un born_in eje a un vertice. Desde ese vértice siga una cadena ejes WITHINs hasta que llegue a una ubicación que sea USA.
- Ese mismo vertice (persona) tenga un eje lives_in que lleve a un vertices que pertenezca a Europa.
Si fuera a hacer la misma consulta en SQL, sería algo asi
-- in_usa es el set de vértices de todas las ubicaciones en USA
in_usa(vertex_id) AS (
SELECT vertex_id FROM vertices WHERE properties->>'name' = 'United States' 1
UNION
SELECT edges.tail_vertex FROM edges 2
JOIN in_usa ON edges.head_vertex = in_usa.vertex_id
WHERE edges.label = 'within'
),
-- in_europe es el set de vèrtices de todas las ubicaciones en Europa
in_europe(vertex_id) AS (
SELECT vertex_id FROM vertices WHERE properties->>'name' = 'Europe' 3
UNION
SELECT edges.tail_vertex FROM edges
JOIN in_europe ON edges.head_vertex = in_europe.vertex_id
WHERE edges.label = 'within'
),
-- born_in_usa es el set de vértices de todas las personas que nacieron en USA
born_in_usa(vertex_id) AS ( 4
SELECT edges.tail_vertex FROM edges
JOIN in_usa ON edges.head_vertex = in_usa.vertex_id
WHERE edges.label = 'born_in'
),
-- lives_in_europe es el set de todas las personas que viven en Europa
lives_in_europe(vertex_id) AS ( 5
SELECT edges.tail_vertex FROM edges
JOIN in_europe ON edges.head_vertex = in_europe.vertex_id
WHERE edges.label = 'lives_in'
)
SELECT vertices.properties->>'name'
FROM vertices
-- join para encontrar a las personas que nacieron en USA y viven en Europa
JOIN born_in_usa ON vertices.vertex_id = born_in_usa.vertex_id 6
JOIN lives_in_europe ON vertices.vertex_id = lives_in_europe.vertex_id;
Triple store & SPARQL → Almacenar toda la información en forma de sentencias de tres partes
- Sujeto
- Predicato
- Objeto
- ejemplo: Juan Gustan bananos
-
Modelo “Lucy vive en Londres”
_:lucy a :Person. _:lucy :name "Lucy". _:lucy :bornIn _:idaho. _:idaho a :Location. _:idaho :name "Idaho". _:idaho :type "state". _:idaho :within _:usa. _:usa a :Location. _:usa :name "United States". _:usa :type "country". _:usa :within _:namerica. _:namerica a :Location. _:namerica :name "North America". _:namerica :type "continent".
Nota. → La web3 (web semantica) se basa en ese modelo
https://www.programstrategyhq.com/post/web3-meaning-explained