Descubre cómo realizamos un Headless Drupal con React para la herramienta de autoevaluación de equipos de Adevinta Spain
Desde Adevinta Spain nos pidieron, al departamento de Drupal de Omitsis, que mejoráramos su herramienta de autoevaluación de equipos (Selft Sufficient Teams o SST). Adevinta es la super empresa que gestiona los marketplaces Fotocasa, habitaclia, InfoJobs, coches.net, motos.net y Milanuncios, cada uno de ellos con millones de visitas. Son unos mega cracks, muy profesionales y además muy majos.
La herramienta estaba en WordPress y les había servido muy bien, pero necesitaban aplicar algunas mejoras. Al evaluarlas, ambos vimos que la mejor solución era crear un backend con Drupal 9 y un frontend con React. Es decir, un Headless Drupal con React.
Una de las ventajas es que ellos, al ser expertos en react, podrían involucrarse en el desarrollo del frontend en cualquier momento. Evidentemente, otra gran ventaja al usar React era que el frontend podía ser más dinámico, con cambios instantáneos a las acciones del usuario.
Y Drupal, como backend, está más que demostrado que es robusto y a la vez flexible a las necesidades del proyecto.
Además, al ser software libre no es necesario pagar unas cuotas mensuales por las licencias de uso como otros CMS propietarios (Adobe Experience Cloud, Liferay, Contentful, etc)
La herramienta
Como resumen rápido del funcionamiento de la herramienta, podemos decir que se trata de una serie de tests de autoevaluación. Cada test consiste en una serie de preguntas agrupadas.
Los usuarios pueden contestar sí o no a una pregunta, y poner un comentario. Según contesten, se le otorga una puntuación por grupo de preguntas y una puntuación final.
Para poder mejorar como equipo se pueden crear acciones, cosas que el equipo piensa les ayudará a pasar del no al sí, y se compromete a hacer.
El backend: Headless Drupal 9
Como ya hemos comentado para el backend usamos la última versión de Drupal 9 con las siguientes características especiales:
Custom entities
En lugar de usar bundles de nodos (tipos de contenido), vimos que tenía más sentido crear entidades custom para cada una de las estructuras requeridas para nuestra aplicación. Esta necesitaba unas especificaciones muy concretas, como la posibilidad de poder crear nuevas plantillas de tests, sin modificar los tests pasados.
Migración desde WordPress a Drupal 9
Como siempre desde Omitsis encaramos una migración usando el fantástico módulo migrate. Para eso, se creó un source plugin para cada entidad a migrar, más unos cuantos process plugins, para poder transformar los datos de WordPress en la forma que Drupal los necesita.
JSON:API
Para poder pasar información entre el frontend y el backend usamos JSON:API, ya en el core de Drupal desde hace un tiempo.
En JSON:API se envía lo que son campos referenciados en un array diferente del array devuelto de los datos de la entidad solicitada. Pongamos que hago una petición de una entidad llamada receta y esta tiene campos de ingredientes.
Cuando le decimos a JSON:API que incluya la información de los ingredientes, no lo hace dentro de cada receta, ya que si tenemos mil recetas y la gran mayoría contienen el ingrediente cebolla, nos incluiría muchas veces la información completa de la cebolla.
En lugar de eso, se incluye una referencia (un id) a la entidad cebolla, pero los campos de la cebolla están en un array de includes y, por lo tanto, solo estará una vez, y ocupará mucho menos.
Pero esto es muy poco práctico cuando se obtiene en el React, ya que por cada entidad hay que buscar todas sus entidades anidadas, y más si hay más de un nivel.
Para eso existe el módulo JSON:API Include pero, después de evaluarlo, lo desestimamos porque hacía que nuestras peticiones, con la arquitectura anidada que teníamos, fueran demasiado pesadas.
En lugar de eso, vimos mucho más eficiente hacer el mismo proceso en cliente con el paquete jsona. Así, teníamos peticiones más ligeras y en React una estructura de datos mucho más práctica.
Además, para mejorar el rendimiento de las peticiones, siempre que fue posible unimos todas las peticiones en una, ahorrándonos el coste de la capa de red de cada petición. Para eso, existe el excelente módulo subrequests, muy bien explicado aquí por Mateu, su autor.También usamos Open API, para que el equipo de frontend tuviera muy claro y fácil conocer todos los endpoints de JSON:API, filtros, etc.
Administración del backend
Para facilitar la administración en el backend se usó el tema Gin, junto con la Gin toolbar. También se creó un simple dashboard con los accesos directos más importantes.
Además, se tuvo especial cuidado con los listados y los formularios de las entidades, para que fueran lo más usables posibles. Y con la ayuda del módulo Corresponding Entity References se facilitó crear referencias cruzadas, tan solo editando desde una parte.
Adevinta nos pidió que, para acceder al backend y al frontend, se pudiese hacer mediante Okta. Para gestionar el SSO (single sign on) con Okta en el backend usamos el módulo SAML SP 2.0 Single Sign On (SSO) – SAML Service Provider
Exportaciones de las tablas de datos
Para integrar los resultados con su data lake, creamos unas exportaciones en csv, que se generaban con el cron (con ayuda de ultimate cron) cada noche. Estas exportaciones se subían directamente a un bucket de S3 de Adevinta, usando el módulo S3 File System.
Frontend con React
La aplicación de React la creamos usando create-react-app. Al ser un frontend privado, no era necesario usar herramientas para crear código estático o que se ejecutara en el servidor (Server Side Rendering)
El diseño de la parte pública lo asumió nuestro fantástico departamento de diseño de Omitsis. Se reunieron con el cliente para conocer sus necesidades y así poder crear los primeros wireframes.
Wireframe de la página principal, donde se muestra el listado de equipos.
Página de un equipo, con sus evaluaciones y acciones.
Wireframe de una evaluación, con los grupos de preguntas y sus resultados.
Wireframe de una evaluación, con zoom en la parte de una pregunta.
Una vez creados y validados, se creó el diseño de cada página, usando el design system de Adevinta:
Adevinta design system
Diseño de la página de equipos
Diseño de la página de un equipo
Diseño de la página de una evaluación
Diseño de la página de una evaluación, zoom en una pregunta
Mejoras de performance del frontend en React
Para conseguir que todo funcionase de la manera más fluida se tuvo en cuenta los siguientes aspectos:
Minimizar peticiones
Mejor siempre dos peticiones que tres, y una aún mejor que dos. Gracias a JSON:API puedes incluir toda la información que necesitas en una sola petición: la entidad, las entidades relacionadas y las relacionadas de las relacionadas. Así, hasta el nivel que queramos.
Incluir solo lo necesario en el JSON:API
Relacionado con el asunto anterior, si añadimos muchas entidades relacionadas con todos sus campos, podemos encontrarnos con una consulta interna muy compleja. Esto puede hacer que necesite mucha memoria y que el archivo JSON resultante sea demasiado pesado.
Para evitar esto, hay que podar todo lo que no se necesite, usando Sparse Fieldsets que permite especificar qué campos queremos que devuelva de cada entidad.
Agrupar peticiones
Como ya hemos comentado antes, usamos profusamente el módulo subrequests para agrupar más de una petición, cuando es posible. Así nos ahorramos el tiempo de la capa de red.
No usar JSON:API Include
También como hemos comentado antes, en nuestro caso usar JSON:API Include no era una buena idea, ya que podíamos tener peticiones que devolvían json demasiado grandes (> a 1MB).
Instalamos el paquete jsona y tan solo haciendo esto:
import Jsona from 'jsona';
const dataFormatter = new Jsona();
Convierte el json que obtenemos del JSON:API en uno más amable para el programador.
const json = {
data: {
type: 'town',
id: '123',
attributes: {
name: 'Barcelona'
},
relationships: {
country: {
data: {
type: 'country',
id: '32'
}
}
}
},
included: [{
type: 'country',
id: '32',
attributes: {
name: 'Spain'
}
}]
};
const town = dataFormatter.deserialize(json);
console.log(town); // will output:
/* {
type: 'town',
id: '123',
name: 'Barcelona',
country: {
type: 'country',
id: '32',
name: 'Spain'
},
relationshipNames: ['country']
} */
Skeleton
En lugar de mostrar loaders, pensamos que era mejor usar un skeleton mientras se cargan los datos. Esto es casi ya un estándar en la industria, ya que el rendimiento percibido por el usuario es mayor.
Single Sing On con Okta
Para acceder al frontend era necesario que los usuarios lo hiciesen validándose con Okta. Para conseguir esto, usamos el paquete Okta React y Okta Auth js.
Los usuarios al entrar al sitio, si no estaban autentificados, eran redirigidos al login de Okta de Adevinta.
Integración continua con amazon
Usando Elastic Beanstalk, CodePipeline, EC2 y CloudWatch de Amazon, nuestro departamento de sistemas creó un proceso de integración continua.
Con CodePipeline se «escucha» a cambios en las ramas del repositorio: master para el entorno de producción, staging para el entorno de staging. Cuando ve que hay un cambio, hace un nuevo deploy usando Elastic Beanstalk.
LLuego Elastic Beanstalk se encarga de crear una nueva instancia de EC2, hace los tests necesarios y si todo va bien, la intercambia por la anterior. Si no pasa los tests, no la reemplaza y la elimina.
Conclusión
Tenemos mucha experiencia creando proyectos de gran calidad, centrados en el usuario, con Drupal y React, pero este proyecto nos gustó especialmente.
Por ser un gran ejemplo de Headless Drupal con React y porque trabajar con Adevinta Spain es un placer: son accesibles, supieron crear unas excelentes especificaciones, rápidos, tienen recursos y son muy buena gente.