Go con Gin, el mega tutorial (IV)

¡Buenas Nómadas! Os presentamos la cuarta entrega de Go con Gin. Hoy hablaremos de bases de datos.

Índice de la serie

 

Bases de datos

Las bases de datos son un tema muy importante, son tan importante como que goblog depende de las bases de datos para almacenar los mensajes, comentarios, usuarios, etc. Sin esa información goblog no tiene ningún sentido.

En el post vamos a ver como como conectarnos con las bases de datos, a integrarlas en Gin y también explicaremos un poco acerca de los ORM y la necesidad de los mismos.

Las basas de datos son muy “suyas” y cada base de datos requiere de su conector para “hablar el dialecto” de la base de datos. Esto es muy importante tenerlo en cuenta, porque hoy podemos estar usando MySQL, pero mañana igual queremos cambiar y utilizar PostgreSQL. Este cambio afectaría bastante a nuestro código y es posible que tuviéremos que rehacer parte del código.

¿Cual es la solución entonces? Utilizar lo que viene siendo un conector ORM. Los ORM además de otras bondades, que ahora contaremos, nos permiten conectarnos a distintas bases de datos indistintamente del tipo de la misma.

¿Qué ventajas tenemos al utilizar ORM?

La verdad que muchas, las bases de datos os las podéis imaginar como una tabla, con sus valores por columnas y cada registro o entrada por fila. Esto es un problema para cuando queremos acceder a los datos de la base de datos. Porque hay que transformar cada fila en un objeto y esta tarea no es tan sencilla como parece. En la siguiente imagen tenéis un gráfico muy básico de como interviene un sistema ORM.

Funcionamiento de un ORM

Resumiendo, es necesario usar un ORM en nuestro proyecto para poder acceder a bases de datos sin la necesidad de modificar el código fuente y sobre todo para poder acceder a los datos de una forma fácil a modo de objetos.

En Go hay varias opciones en ORM, pero nosotros hemos elegido GORM.

¿Cómo integramos GORM en nuestro proyecto?

Aquí se complica un poco la cosa, vamos a tener que modificar el código de nuestro proyecto, creando carpetas y ficheros varios. ¡Vamos a ello!

Te puede interesar  Telegram bots en golang (III)

Primero deberemos incorporar la librería gorm a nuestro proyecto. Para ello ejecutamos dep ensure -add github.com/jinzhu/gorm. Después de incorporar la librería al proyecto creamos la carpeta webapp/database y el fichero database.go dentro del directorio recién creado.

También crearemos un fichero, por el momento vacío, en el directorio webapp/models/database.go.

Ahora ya vamos a empezar a “picar” código. El primer fichero a modificar es webapp/database/database.go.

En este fichero vamos a crear una variable global (línea 11) desde la que más tarde podremos acceder a la base de datos. Además, crearemos una función, Init, para iniciar la conexión a la base de datos.

Nosotros, por sencillez, utilizamos una base de datos SQLite, pero es perfectamente válido utilizar MySQL o PostgreSQL, por ejemplo.

Usamos la cadena de conexión en la función gorm.Open. Esta función necesita como primer parámetro el tipo de la base de datos y en el segundo parámetro donde se encuentra la base de datos. Para SQLite es tan simple como indicarle una ruta a un fichero.

En esta misma función de inicialización (Init) utilizamos la funcionalidad de auto migrado que nos ofrece gorm. Esto nos será útil para crear las tablas en caso que no estén creadas o a actualizar la tabla en caso de ser necesario.

Migración de bases de datos

Las aplicaciones web están “vivas” siempre hay actualizaciones, cambiar cosas, añadir funcionalidades, etc. Todo esto implica en muchas ocasiones modificar la base de datos y ¿Qué pasa si tenemos una tabla ya creada con información en la base de datos y queremos añadir una columna más? Bien, ésta es la pregunta del millón. Porque ¿qué pasará con los datos que ya están en la tabla? y ¿Como vamos a crear una columna más o menos?

Bien, pues hay aplicaciones que se encargan justamente de esto. Lo más habitual es, dependiendo de la base de datos, guardar toda la información, borrar la tabla (vieja), crear una tabla nueva con la nueva columna, por ejemplo, y volver a cargar los datos anteriores. También es posible, según el sistema gestor de bases de datos, que se pueda modificar la tabla añadiendo la columna sin tener que eliminar y crear la tabla.

Nosotros al utilizar gorm tenemos una funcionalidad de migrado muy básico, es posible utilizar otras herramientas como go-migrate. Que son más especificas para migrar esquemas de bases de datos.

Ahora ya volvemos a goblog.

Ya tenemos la base de datos inicializada. Ahora debemos de llamar a la función Init desde algún punto y también deberemos de tener disponible la conexión a la base de datos en cada uno de los handlers.

Para inicializar la base de datos utilizaremos el main situado en cmd/goblog/main.go. Aquí añadiremos las líneas 9 y 10 del siguiente código.

Con esto inicializaremos la conexión a la base de datos y además le indicaremos a Go que puede cerrar la conexión cuando no existan referencias a database.DB con la instrucción defer database.DB.Close().

Por último, tenemos que decir a Gin que en cada petición que recibamos de un usuario añada en el contexto la base de datos. Así en cada handler tendremos acceso a la base de datos.

Te puede interesar  Puesta en marcha de Odoo en Debian - Parte 2

Para conseguir esto usaremos un handler especial, el cual nos permitirá insertar la base de datos en el contexto para todos los handlers restantes indistintamente de cual sea.

Crearemos la siguiente función en el fichero webapp/router/main.go.

Esta función es en realidad muy simple porque estamos devolviendo en el return una función anónima que cumple el esquema de los handlers vistos hasta ahora, lo único que no tiene un nombre.

Dentro de la función anónima añadimos un puntero a la base de datos en el contexto de gin. Esto lo hacemos con c.Set(«db», database.DB) e indicamos que la ejecución puede continuar al siguiente handler con c.Next().

Para finalizar con este fichero tenemos que indicar a gin que utilice nuestro handler “especial”. Esto es añadiendo la siguiente línea antes del return: srv.Use(ResisterDatabase()).

Ahora paramos de “picar” código y volvemos a la teoría, tranquilos que luego volvemos al código.

Modelo de la base de datos

Bien, ya hemos hablado de las bases de datos y de los ORM, así como que éstos son necesarios para poder leer la información de la base de datos a modo de objetos. Pero también tenemos que pensar y definir la base de datos y su esquema.

Para crear los siguientes esquemas hemos utilizado la siguiente herramienta http://ondras.zarovi.cz/sql/demo/.

El primer modelo que vamos a crear es el modelo para representar a los usuario. Este modelo es bastante sencillo. Dispondremos de un campo id, el cual lo veremos en todos los modelos. Este campo será la clave primaria del modelo.

Los campos restantes son bastante auto-explicativos. Tenemos el nombre del usuario, el correo electrónico y la contraseña. El tipo de dato para estos campos es VARCHAR. Esto significa que podrán contener casi cualquier carácter y además hemos añadido un limite en la longitud, principalmente por dos motivos, primero por lógica. No tiene sentido que un nombre pueda tener 2000 caracteres, sino más bien unos 20 o 30. Segundo, las bases de datos deben de ser óptimas y una forma de hacerlo es afinando cada capo  de los modelos.

Antes de continuar nos gustaría que prestaseis atención al campo password. Este campo a pesar de ser un VARCHAR y poder almacenar casi cualquier valor, no debemos almacenar el password tal cual nos lo proporciona el usuario, en la jerga sería en texto plano. Esto es así porque si alguna vez pasará que nos hackean la web y consiguen robarnos la base de datos, los atacantes tendrían los datos de todos los usuarios, sus nombres, correos y contraseñas. Así que deberemos de cifrar las contraseñas y almacenar un hash criptográfico, ya veremos como hacerlo más adelante. Nada más esto era necesario contarlo ahora.

Te puede interesar  Traefik, proxy inverso con Let's Encrypt en Raspberry Pi

Representación del modelo en Go

Usuarios

¿Cómo representamos la tabla del modelo de usuarios en Go? Si pensamos un poco lo mejor será usar un tipo estructurado (struct).

El “struct” que vamos a crear es el siguiente:

Si os fijáis no tenemos el campo id y en cambio, en su lugar, tenemos el campo *gorm.Model. Este campo proporciona de forma “automática” el campo id y algún campo más y que son muy útiles.

Lo cierto que solo con la información de los usuarios no podemos hacer mucho, así que debemos de expandir la base de datos y registrar más información sobre los posts que se publican por parte de un usuario.

Relación con otras tablas

Esta relación, la que hay entre los usuarios y los posts que publican, se llama uno a muchos. Significa que un usuario puede ser autor de muchos posts y a la vez, un post solo pertenecer a un usuario.

Clave ajena usuarios post

En bases de datos las relaciones se consiguen mediante las claves ajenas. Así pues en la tabla Posts hemos añadido el campo Body, el cual almacenará el post, y además, hemos añadido los campos UserUserID. El campo UserID además de ser de tipo entero los valores de este campo serán los valores del id de la tabla Users. De este modo se puede enlazar usuarios que han escrito un post, porque con el campo UserID se podrá buscar al usuario en concreto que creo el post.

Esto lo representamos en Go del siguiente modo.

El código anterior hace referencia al fichero webapp/models/database.go.

Ya finalizamos, os lo aseguro.

Solo nos queda modificar el fichero webapp/database/database.go para actualizar la línea donde teníamos db.AutoMigrate(&models.User{}) por db.AutoMigrate(&models.User{}, &models.Post{}).

Esta última modificación la hacemos para que GORM actualice de forma automática las tablas, sus relaciones y cree las nuevas tablas.

Y Nómadas, si habéis llegado hasta aquí os felicito, este ha sido un post duro de digerir.

Con esto hemos terminado por hoy, volveremos con más pronto

¡Saludos!

Go con Gin, el mega tutorial (IV)
Etiquetado en:                 

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *