Ecto 101: Intro, configuración y manipulación de datos sin esquemas

Hay un nuevo jugador en la gran liga de lenguajes de programación y su nombre es Elixir. Elixir es un lenguaje moderno, dinámico, funcional, pensado para ser distribuido y para crear aplicaciones tolerantes a fallas. Y todo buen lenguaje productivo necesita de una buen biblioteca para trabajar con bases de datos y es aquí donde juega su rol Ecto.

En este articulo quiero dar una pequeña introducción a las funcionalidades de Ecto 2 para configurar y realizar consultas a una base de datos sin usar esquemas, en futuros posts incluiré temas para aprender a mapear estructuras de Elixir a tablas de una base de datos relacional e incluso el tema de versionamiento de la base de datos usando migraciones. Para entender este post se requiere conocimiento básico sobre el lenguaje de programación Elixir y su herramienta Mix.

Empecemos.

Ecto

Ecto es un DSL (Domain Specific Language, por sus siglas en ingles) para escribir consultas e interactuar con bases de datos relacionales usando Elixir.

Es una herramienta que tomó algunas ideas prestadas de LINQ de .NET. LINQ es un lenguaje integrado de consultas que agrega funcionalidades de consultas de propósito general al framework .NET. LINQ puede ser usado para diversos tipos de información, no solo datos relacionales o datos XML. Ecto también agrega un lenguaje integrado de consulta a Elixir pero a diferencia de LINQ su propósito es mas especifico, está pensado para ser usado sólo con base de datos relacionales.

¿Lenguaje integrado de consulta?

Si, exactamente eso. Con un lenguaje integrado de consulta en lugar de escribir SQL como el siguiente:

Escribimos:

Esto ofrece algunas ventajas como las siguientes:

  • Metadatos. Las estructuras y atributos tienen asociados la tabla/columna a la que están mapeados así como su tipo.
  • Validación de sintaxis en tiempo de compilación. Si existe un error al escribir la consulta, el mismo compilador nos indicara donde.
  • Tipos. Al hacer el mapeo cada atributo tiene un tipo y si por alguna razón nos equivocamos a la hora de asignar un valor el mismo compilador nos lo dirá.
  • Composición de consultas. Siguiendo ciertas reglas, podemos crear consultas que podemos ir armando poco a poco y después ejecutarlas como una sola entidad.

Componentes principales de Ecto

Ecto esta divido en 4 componentes principales:

  • Ecto.Repo. Un repositorio en por el cual consultamos, insertamos, actualizamos y borramos entradas en la base de datos. Un repositorio hace uso de un adaptador que implementa el comportamiento (behaviour) Ecto.Adapter para poder comunicarse con la base de datos.
  • Ecto.Schema. Los esquemas son usados para realizar el mapeo de tablas de una base de datos a estructuras de datos de Elixir. Realizar el mapeo usando esquemas no es necesario para poder hacer uso de Ecto y en este post veremos como lo podemos hacer.
  • Ecto.Changeset. Hay que recordar que Elixir es un lenguaje funcional que trabaja con estructuras de datos inmutables. Los changesets proveen mecanismos para dar seguimiento y validar los cambios aplicados a los datos.
  • Ecto.Query. Las consultas son usadas para obtener datos de la base de datos usando el propio lenguaje Elixir. Las consultas de Ecto son seguras y ayudan a evitar problemas como inyección de SQL al mismo tiempo que se puede construir parte por parte.

Configuración de Ecto.Repo y su adaptador

Iniciaremos creando un proyecto usando Mix:

Mix es una herramienta que provee de tareas para crear, compilar y ejecutar proyectos de Elixir, administrando las dependencias del mismo entre otras cosas mas.

El comando anterior crea un proyecto vacío. La opción sup le dice a Mix que debe crear el esqueleto de una aplicación OTP incluyendo su árbol de supervision.

¿Aplicación OTP? ¿Árbol de supervision?

OTP (Open Telecom Platform) es un framework que viene desde Erlang que ayuda a crear aplicaciones concurrentes y que incluye muchas bibliotecas que ya han sido probadas para la fácil implementación de este tipo de aplicaciones.

Una de las maravillas de Erlang (y que hereda Elixir) es poder implementar aplicaciones altamente concurrentes usando procesos. Los procesos de Erlang son entidades que se ejecutan de manera concurrente, son ligeros, no comparten memoria entre sí y no usan mucha memoria (alrededor de kilobytes). Por lo mismo que no comparten memoria, los procesos se comunican entre ellos enviando mensajes. Un árbol de supervision de procesos es otro tipo de proceso que su única tarea es monitorear la ejecución de otros procesos y si éste detecta que algún proceso murió (de acuerdo a la estrategia configurada), éstos pueden crear nuevos procesos que sustituyan a los que ya no se están ejecutando. Este tipo de características es la que ayuda a crear aplicaciones tolerantes a fallas.

Inicialmente el proyecto debe lucir así:

En la raíz del proyecto se encuentra el archivo mix.exs que es donde podemos definir las dependencias necesarias para incluir Ecto a nuestro proyecto, además del adaptador a la base de datos (en este caso, Postgres).

Editaremos el archivo mix.exs para incluir las dependencias de Ecto y postgrex que es el adaptador para Postgres. Las dependencias del proyecto se definen en la función deps:

También debemos especificar, en la función application, que una vez iniciada la aplicación ésta debe iniciar la aplicación de Ecto y del adaptador (que son aplicaciones que se ejecutan en sus propios procesos):

Lo siguiente es crear nuestro repositorio. Para esto hay que crear un nuevo archivo, le daremos el nombre de repo.ex, dentro del directorio /lib con el siguiente contenido:

Lo anterior está definiendo un módulo con el nombre EctoTest.Repo que hace “uso” de Ecto.Repo y especifica el nombre de la aplicación OTP que tiene la configuración de repositorio.

Ahora definiremos esa configuración del repositorio en el archivo /config/config.exs agregando lo siguiente:

Haciendo uso de las funciones config/2 y config/3 del modulo Mix.Config se pueden configurar aplicaciones. En el anterior código se esta configurando primero el repositorio EctoTest.Repo de la aplicación :ecto_test especificando el adaptador que se usará y las credenciales necesarias para conectarse a la base de datos. Después se configura la aplicación :ecto_test para indicar los repositorios de Ecto via la opción ecto_repos.

En este post no tocaremos el tema de las migraciones de Ecto que ayudan a crear las tablas de la base de datos y llevar un versionamiento de la misma. Por lo mismo, va a ser necesario crear a mano la base de datos ecto_test y las siguientes tablas para poder hacer nuestros experimentos:

Por último, hay que configurar en el árbol de supervisión el proceso de Ecto. Ecto a su vez es una aplicación que tiene un árbol de supervisión donde entre los procesos supervisados, está el pool de conexiones a la base de datos.

En el archivo /lib/ecto_test.ex se encuentra definido el módulo de la aplicación OTP: EctoTest. En este modulo se tiene la función start/2 que define los procesos “hijos” a ser iniciados y supervisados si así se configura:

La función supervisor define el modulo que se le pasa como argumento como un proceso supervisor, en este caso EctoTest.Repo. Hay que notar que la estrategia de supervision configurada es :one_for_one, esto quiere decir que si por alguna razón el proceso del repositorio de Ecto se muere, sólo un proceso nuevo será creado para sustituir al anterior.

Manipulando datos con Ecto

Ok. Ya tenemos todo configurado y en su lugar. Incluso la base de datos y las tablas necesarias ya fueron creadas. Lo siguiente es levantar la aplicación usando el REPL de Elixir para poder realizar nuestros experimentos con el siguiente comando:

Lo primero que haremos será obtener datos de la tabla department, en el REPL hay que ejecutar los siguientes comandos:

Ejecutamos alias EctoTest.Repo para poder crear un alias y no tener que escribir todo el nombre del módulo cada vez que necesitamos hacer uso del repositorio. import Ecto.Query importa las funciones, macros y tipos que existen en este módulo para su uso en la sesión del REPL, en este caso la macro from.

La macro from crea una consulta. Una consulta (o query) es valor que implementa el protocolo Ecto.Queryable y que la función all del repositorio puede entender para ejecutarla en la base de datos.

En este caso, al ejecutar:

Estamos ejecutando una consulta a la base de datos que en lenguaje SQL sería similar a:

Notarán que en la opción select especificamos una lista con las columnas especificas que queremos seleccionar. Esto es necesario ya que el adaptador de Postgres mandara un error si queremos realizar una consulta como la siguiente:

Realizamos una consulta pero no obtuvimos datos de ella, esto es porque nuestra base de datos esta vacía. Vamos a agregar un registro en la tabla department usando la función insert_all de nuestro repositorio:

La función insert_all nos ayuda a insertar datos a una tabla especificando una lista de keywords o un mapa. En este caso usamos una lista de keywords.

Si ahora volvemos a ejecutar:

Ahora si obtenemos datos.

Insertemos algunos empleados en la tabla employee y otro departamento en department:

Ahora, digamos que nos equivocamos y queremos mover de departamento a algunos empleados:

La función update_all recibe un consulta (valor que implementa el protocolo Ecto.Queryable) y las actualizaciones a los valores que se van a modificar.

Si consultamos los empleados en el departamento R&D haciendo un join con la tabla de departamento para obtener su nombre:

Veremos que ahora solo los empleados 2 empleados de los 3 en la tabla pertenecen al departamento R&D.

Ahora borremos al empleado con apellido Wayne:

y veamos que empleados quedan en la tabla employee:

Ahora solo quedan dos empleados en la tabla employee.

Todo el código usado en este post se encuentra en el repo de github:

Conclusión

En esta introducción a Ecto, vimos cómo iniciar un proyecto y configurarlo para poder conectarse a una base de datos Postgres y empezar a manipular datos sin la necesidad de los esquemas de Ecto. Y como ya lo mencioné al principio, en futuros post tocaremos los temas de migraciones de Ecto para poder llevar un versionamiento de la base de datos usando el mismo lenguaje Elixir y aprenderemos a hacer el mapeo de la base de datos a estructuras de datos de Elixir.

Rafael Gutiérrez, Software Engineer @Nearsoft