Administración de Hasura a fondo

ene. 30, 2023

Este artículo forma parte de una serie:

Hemos visto las funcionalidades básicas para gestionar nuestros datos que GraphQL nos ofrece. Las operaciones como Query, Mutation y Subscription de GraphQL nos permiten obtener, crear, actualizar y eliminar datos con una sintaxis muy sencilla y fácil de aprender.

Ahora vamos a ver las diferentes opciones que Hasura nos proporciona por defecto para gestionar la base de datos, ejecutar tareas en el lado del servidor o conectar con servicios externos.

Para este ejemplo vamos a descargar una base de datos ya creada con datos de muestra, concretamente la que contiene información de un negocio de alquiler de películas llamada Pagila.

Simplemente tenemos que importar el esquema y los datos de ejemplo con psql. Después podemos crear la conexión a la base de datos virtualizada desde el panel de Hasura como vimos en el primer capítulo de la serie.

En el fichero docker-compose tenemos que añadir la cláusula ports al apartado de postgres para poder conectar desde fuera del contenedor virtualizado:

Panel de administración

version: '3.6'
services:
  postgres:
    image: postgres:12
    ports:
      - "5432:5432"
    volumes:
    ...

Explorador de API

Es la primera pantalla que nos encontramos al entrar en el panel de administración de Hasura y en ella tenemos varias partes bien diferenciadas, en la parte superior está la dirección URL de nuestro endpoint y los parámetros incluidos en la cabecera de la petición de la API.

A la izquierda el explorador para navegar por las diferentes entidades, GraphiQL para realizar consultas en el centro y la documentación dónde consultar los diferentes parámetros y opciones disponibles a la derecha. Abajo escribiríamos los valores de las variables en formato JSON si se realizan peticiones con variables en GraphiQL.

En el anterior capítulo ya vimos múltiples ejemplos que podemos trasladar y adaptar para ejecutar directamente desde aquí o podemos utilizar el explorador para construir nuestra consulta con las diferentes opciones de filtrado, selección y ordenación del mismo.

Datos

Nada más acceder a la segunda pestaña nos encontramos con un administrador de datos a la izquierda y a la derecha las tablas de las que se está haciendo seguimiento. Abajo a la izquierda hay un botón que nos abrirá una ventana para ejecutar consultas SQL de forma manual.

Explorar filas

Al seleccionar alguna de las entidades en el administrador de la izquierda se nos mostrarán los registros que la tabla contiene además de permitirnos añadir nuevas filas o modificar las existentes, las relaciones entre las entidades y los permisos de acceso a los datos a través de la API.

Insertar fila

En este punto simplemente tenemos que rellenar los campos para introducir un nuevo registro en la tabla seleccionada.

Modificar

La pestaña Modify nos permite cambiar los parámetros de las diferentes columnas de la tabla así cómo añadir nuevas, los triggers, las restricciones, índices y demás, también nos permite establecer la tabla como un tipo enum para datos fijos o crear un campo calculado.

Relaciones

Nos permite gestionar las relaciones entre las diferentes entidades de nuestra base de datos de forma independiente.

Permisos

La sección de roles y permisos sirve para establecer los controles de acceso a los datos a través de las consultas a la API, quién puede insertar, acceder, modificar o eliminar qué datos y cómo, las variables que puede consultar, etc. Esto utilizando un sistema de roles y permisos lo suficientemente flexible cómo para cubrir la mayoría de casos de uso básicos.

Por ejemplo un rol de usuario simple con acceso de consulta y modificación limitada, otro de tipo moderador que pueda modificar algunas tablas que el usuario no o un rol de usuario para los empleados que tengan permitido el acceso a determinadas tablas que el usuario básico y los de moderación no, etc.

Si seleccionamos en la izquierda la tabla city podremos crear los roles de usuario, en el campo de texto que pone Enter new role (Introducir nuevo rol) escribimos user y pulsamos sobre la cruz roja que hay en la columna Select, esto nos abrirá el formulario de configuración de los permisos:

  • Row select permissions: Nos permite establecer algún filtro utilizando las variables de la cabecera de la solicitud u otros campos de la base de datos, lo dejamos sin restricciones activando Without any checks.
  • Column select permissions: Indica qué parámetros de la entidad se verán influenciados por esta regla. Presionamos el botón Toggle all para que nos active todos los campos haciéndolos así accesibles para este rol.
  • Aggregation query permissions: Nos permite indicar si este rol de usuario tiene permisos para hacer consultas de agregación, como totales o promedios, máximos o mínimos. Podemos dejarlo como esta´.
  • Root field permissions: Para gestionar los permisos del elemento raíz, si está desactivado se permiten todos por defecto.

De esta forma habremos creado el nuevo rol de usuario user con permisos de selección en la tabla city sin ningún tipo de restricción en los parámetros a seleccionar, ahora podemos repetir el proceso para crear el rol staff y darle los permisos de inserción, selección y modificación necesarios.

Para verificar que los permisos se están comprobando correctamente podemos ir a la pestaña API y añadir a la cabecera de la petición la clave x-hasura-role con el valor user, veremos que sólo podemos recoger los datos de city mientras que si lo cambiamos por staff podemos acceder (además de insertar y modificar) a mayores a la información de la tabla staff.

Acciones

Una acción es un evento que se ejecuta en el servidor tras una llamada a la API, nos permite ejecutar determinadas acciones que no corresponderían con las habituales, fuera de la lógica de negocio.

Imaginemos que en una de nuestras aplicaciones queremos mostrar el tiempo actual en una ciudad concreta, podríamos llamar directamente a Free Weather API de OpenMeteo desde el cliente o crear una acción en nuestro Hasura que actuaria de intermediaria y se encargaría de solicitar a OpenMeteo la temperatura cuando la llamásemos desde el cliente.

Este tipo de funcionalidades están pensadas para realizar gestiones relacionadas con nuestros datos que pueden requerir de algún tipo de operación especial, tal y cómo lo estamos haciendo ahora tiene el inconveniente de que se realizan dos peticiones en vez de la única que necesitaríamos si hiciéramos la petición directamente. Aunque aunque también tiene la ventaja de que de producirse un error o requerir algún cambio en la petición no sería necesario actualizar las aplicaciones cliente.

Es responsabilidad del equipo de desarrollo estimar ventajas y desventajas para decidir qué método es mejor para la casuística del proyecto en cuestión, esto es sólo un ejemplo.

Para empezar vamos a definir el formato de la acción, la solicitud que se realizará, en el recuadro Action Definiton.

type Query {
  getLatLongTemperature(
    latLongInput: LatLongInput!
  ): LatLongOutput
}

En el siguiente recuadro Type Configuration creamos los tipos de datos, podríamos asociarlos con el concepto de Clase de la programación orientada a objetos, definen la estructura de los datos que se recibirán (Input) y responderán (Output) durante la ejecución de la acción.

input LatLongInput {
  latitude: Float!
  longitude: Float!
}

type LatLongOutput {
  latitude: Float!
  longitude: Float!
  current_weather: CurrentWeatherOutput
}

type CurrentWeatherOutput {
  temperature: Float!
  windspeed: Float!
  winddirection: Float!
  weather_mode: Int!
  time: String
}

Tanto LatLongOutput como CurrentWeatherOutput (que es un subconjunto de datos del primero) se corresponden a la respuesta que recibiremos de la API de OpenMeteo, que es similar a la que se muestra a continuación:

{
  "latitude": 51.5,
  "longitude": -0.120000124,
  "generationtime_ms": 0.2110004425048828,
  "utc_offset_seconds": 0,
  "timezone": "GMT",
  "timezone_abbreviation": "GMT",
  "elevation": 27.0,
  "current_weather": {
    "temperature": 5.0,
    "windspeed": 5.9,
    "winddirection": 259.0,
    "weathercode": 0,
    "time": "2023-01-30T20:00"
  }
}

Aquí recogemos las variables que puede que vayamos a utilizar en algún punto en alguno de los clientes, después en cada petición podremos especificar qué datos queremos exactamente en cada caso.

En el Webhook Handler tenemos que añadir la URL del endpoint al que se enviará la petición, la de la API de OpenMeteo: https://api.open-meteo.com

Más abajo vamos a selecionar la opción para modificar las opciones de la solicitud (Add request options transform) que se envía a OpenMeteo para indicarle que queremos el tiempo actual de la latitud y longitud que le enviaremos.

  • Request method: GET
  • {{$base_url}}: /v1/forecast
  • current_weather: true
  • latitude: {{$body.input.latLongInput.latitude}}
  • longitude: {{$body.input.latLongInput.longitude}}

Utilizando {{$body.input.latLongInput.latitude}} accedemos al valor de la latitud en el cuerpo de la solicitud de entrada, una vez hayamos terminado debería mostrarnos una dirección temporal en el Preview de abajo:

https://api.open-meteo.com/v1/forecast?longitude=10&latitude=10¤t_weather=true

Enlace que podemos consultar directamente en el navegador para comprobar que funciona y devuelve los datos que esperamos.

Ahora podemos probar la acción desde el panel de administración de Hasura, en la pestaña API introducimos la siguiente consulta:

query GetLatLongTemperature {
  getLatLongTemperature(latLongInput: {latitude: 10.4806, longitude: 66.9036}) {
    latitude
    longitude
    current_weather {
      temperature
      time
      weather_mode
      winddirection
      windspeed
    }
  }
}

Podríamos también limitar el acceso a esta función utilizando los permisos igual que con las tablas de la base de datos. Suponiendo que la temperatura fuera una funcionalidad sólo disponible para los usuarios del personal se la restringimos para que sólo su rol pueda acceder desde la pestaña Permissions dentro de las opciones de la acción.

Esquemas remotos

Con los esquemas remotos podemos conectar esquemas de GraphQL de otras fuentes por si tuviéramos alguna API GraphQL a mayores y quisiéramos unificarlas.

Eventos

Los eventos a diferencia de las acciones se ejecutan automáticamente al realizarse una operación en la base de datos ya sea desde la API o desde cualquier otra fuente, el equivalente a los triggers en PostgreSQL.

Se utilizan principalmente para llamar a funciones serverless o webhooks que se ejecutarán cuando se produzca un evento. Para poder probarlo vamos a entrar en Webhook.site y copiar la URL que nos proporciona para ponerla en la configuración de nuestro evento.

Rellenamos el formulario de la siguiente forma:

  • Event Name (Nombre): Le he puesto “cityInsertEvent” pero puedes ponerle el nombre que quieras
  • Database (Base de datos): “Films DB”, el nombre que le hayamos puesto al crearla
  • Schema (Esquema): “public”
  • Table (Tabla): “city” o la tabla en la que queramos que se ejecute el evento
  • Trigger Operation (Operación): Seleccionamos Insert para que nos notifique cada vez que se inserta una nueva ciudad
  • Webhook URL (URL del Webhook): La dirección URL proporcionada por Webhook.site

Ahora basta con insertar desde cualquier medio (el panel de Hasura mismo) una ciudad nueva para que nuestro evento se ejecute y nos notifique automáticamente en el servidor que le hemos indicado con la URL. En el panel de Webhook.site nos aparecerá toda la información de la solicitud recibida con los datos que se insertaron si todo ha ido bien.

Al seleccionar el evento que acabamos de crear en el panel de administración de Hasura podemos ver las solicitudes procesadas, pendientes y los logs que guardan un registro de las llamadas a la función.

Los Cron triggers nos permiten ejecutar eventos periódicamente, por ejemplo para realizar una limpieza de la base de datos y los One-off scheduled events nos permiten ejecutar eventos programados en un momento determinado.

Conclusiones

Hemos hecho un repaso a casi todas las funcionalidades disponibles en el panel de control de Hasura y hemos incidido en partes como las acciones, eventos y roles ya que son partes no tan fundamentales como la gestión de tablas por defecto pero aportan un valor añadido que puede ser muy útil en muchos casos.

Desde las consultas directamente en el explorador de la API hasta la gestión de roles y permisos, una poderosa herramienta que conllevaría muchas horas de trabajo para ser desarrollada de forma independiente.

Las acciones nos permiten ejecutar una función predefinida y normalmente ajena a la lógica de negocio de la aplicación, muy parecido a lo que hacen los eventos, enviar llamadas a funciones sin servidor u otros puntos externos sólo que de forma automática tras producirse algún evento en la base de datos.

Relacionado

Artículos relacionados

Quizá te puedan interesar

December 1, 2023

Descubriendo Docker

Docker es una plataforma de virtualización que ofrece una metodología única para empaquetar y …

leer más