Estructuras de datos sin esquema

El auge de las bases de datos NoSQL ha hecho que plantearse almacenar información sin utilizar un esquema de datos prefijado deje de ser algo exótico y pase a ser una opción más a tener en cuenta a la hora de diseñar aplicaciones.

Esta idea de almacenar información sin definir una estructura rígida para contenerla no es algo exclusivo de las bases de datos, sino que también se puede aplicar a otras áreas de una aplicación.

Definición de datos en distintos lenguajes

Los lenguajes de programación ofrecen distintos mecanismos para representar los datos sobre los que deben operar las aplicaciones. Generalmente existe una serie de tipos básicos (números, cadenas de texto, vectores, etc.) que podemos combinar para definir estructuras más complejas, como distintos tipos de colecciones, clases, etc.

Cuando se trabaja con lenguajes estáticos orientados a objetos como C# o Java la práctica habitual es definir un buen número de clases en las cuales se agrupan datos y operaciones sobre esos datos, creando un esquema rígido en el que organizar la información. Podríamos decir que un lenguaje estático favorece la creación de esquemas prefijados.

Por ejemplo, si queremos definir una estructura de datos para representar un punto en un plano, en C# seguramente tendríamos algo parecido a esto:

public struct Point
{
	public readonly int X;
	public reaodnly int Y;
	
	public Point(int x, int y) 
	{
		X = x;
		Y = y;
	}
}

var point = new Point(10, 25);

Los lenguajes dinámicos son bastante diferentes en este sentido. Aunque también podamos definir clases, como en Ruby, u objetos, como en Javascript, el hecho de no existir comprobación de tipos en tiempo de compilación hace que sea mucho más frecuente en lenguajes dinámicos utilizar estructuras de datos definidas al vuelo para usarlas como parámetros o valores de retorno de distintas operaciones.

En Javascript es habitual ver APIs que reciben como parámetro un objeto en el que es esperan encontrar determinadas propiedades, pero que no está definido formalmente en ninguna parte. Realmente se está empleando el objeto como una tabla hash o map de propiedades y valores. En Ruby directamente se usan tablas hash para esos casos.

El mismo punto que definíamos en el ejemplo anterior podríamos hacerlo en Javascript de un par de formas:

// Usando una función constructora
function Point(x, y) {
	this.x = x;
	this.y = y;
}
var point = new Point(10, 25);

// ... O seguramente más habitual
var point = { x: 10, y: 25 };

Durante las últimas semanas he estado bastante expuesto a código en Clojure, y aunque Clojure es un lenguaje que permite definir varios tipos de estructuras de datos, incluyendo clases, es muy habitual (y hasta idiomático) ver código que almacena toda la información usando maps anidados, de una manera muy similar a como hace Javascript con objetos literales, o incluso estructuras aún más simples como vectores:

; Usando un record
(defrecord Point [x y])
(def point (Point. 10 25))

; Más habitual: usando un map
(def point { :x 10 :y 25 })

; También frecuente: usando un vector
(def point [10 25])

En los últimos casos se trata de lenguajes diseñados para definir estructuras de datos al vuelo y cuentan con una sintaxis tersa para definir estos agregados de información de forma cómoda. En C# se puede hacer algo parecido usando objetos anónimos, como de hecho ocurre con varias APIs relativamente recientes (ASPNET MVC, Castle Windsor), pero trabajar con ellos no es lo más cómodo del mundo (a menos que uses dynamic, claro).

Resumen

En esta pequeña introducción hemos visto lo que son las «estructuras de datos sin esquema» y hemos comparado la forma «natural» de representar datos en tres lenguajes bastante distintos. En próximos posts analizaremos un poco las ventajas e inconvenientes que tienen estas diferentes formas de representar información.