Llevar a cabo un buen desarrollo frontend a medida que este va creciendo puede resultar, en muchos casos, un gran reto para el equipo involucrado. Es posible que si el producto en el que participamos avanza con rapidez, nos parezca cada vez más difícil escalar el desarrollo, permitiendo a su vez que todos los equipos sigan trabajando con el ritmo y la calidad habitual.

En este artículo te explicaré la idea de los microfrontends con el fin de dividir nuestra interfaz, con un enfoque monolítico, en diferentes módulos por funcionalidades e implementarlos de manera independiente (tal y como sucede con los microservicios en la parte del backend). Comentaremos las ventajas y desventajas de esta arquitectura enseñando a su vez una aplicación de ejemplo para entender visualmente el concepto.

¿Qué son los microfrontends?

El término microfrontends surge por primera vez en 2016 en ThoughtWorks Technology Radar, una guía sobre las tecnologías de vanguardia, extendiendo el concepto de los microservicios del backend hacia el lado del frontend. Desde entonces, el término se ha utilizado ampliamente en la comunidad de desarrollo web y se ha convertido en un tema de interés para muchos desarrolladores y empresas que buscan mejorar la escalabilidad, la modularidad y la flexibilidad de sus aplicaciones.

En las aplicaciones web de hoy día, el frontend adquiere cada vez más importancia y el enfoque de un monolito tradicional termina por hacerse complejo de gestionar y mantener en el largo plazo. Es por este motivo que, a partir de aplicar la arquitectura de microservicios en el backend dividiendo el desarrollo en servicios independientes, se opta por aplicar este mismo patrón en la parte del frontend tratando de componer la interfaz mediante distintos módulos o aplicaciones especializadas en funcionalidades concretas.

En la siguiente imagen podemos distinguir la evolución de las distintas arquitecturas, desde un monolito compacto sin capas intermedias involucradas, hasta una arquitectura de microservicios en la parte del backend combinada con los microfrontends en su parte frontend.

Evolución desde un monolito compacto hasta una arquitectura de microservicios.

La idea principal tras la arquitectura de microfrontends es concebir el sitio web como un conjunto de aplicaciones independientes, cada una de las cuales se desarrolla y despliega de forma autónoma por su respectivo equipo de desarrollo.

Como resultado, cada equipo forma parte de un área de negocio y desarrolla su funcionalidad de manera independiente al resto y sin dependencias externas. Esto permite una mayor flexibilidad y reduce el acoplamiento entre los diferentes módulos o aplicaciones que componen el sitio.

Martin Fowler, reconocido autor e ingeniero de software, describe esta técnica de la siguiente manera: “un estilo arquitectónico en el que aplicaciones frontales independientes se integran en un conjunto mayor”.

Este enfoque en la organización de los equipos puede resultar especialmente útil en proyectos grandes con muchos desarrolladores involucrados, puesto que al dividir los equipos en distintas funcionalidades evitamos la constante comunicación y coordinación entre los mismos.

Ventajas de los microfrontends

  • Equipos autónomos: dado que cada equipo está enfocado en una funcionalidad concreta del producto, estos tendrán total libertad de desarrollo sin tener que depender de otros que puedan frenar su ritmo.
    Un equipo con foco único en un componente o página específica de la aplicación, será capaz de llevar a cabo entregas de valor más rápidas y efectivas, evitando retrasos y mejorando su calidad.
  • Mayor escalabilidad y confianza en el desarrollo debido a que el código de cada microfrontend está desacoplado del resto, reduciendo la probabilidad de que surjan bugs. Si un microfrontend presentara fallos, este no afectará al conjunto global de la aplicación, reduciendo el riesgo únicamente a ese dominio del negocio.
  • Despliegues independientes: cada microfrontend tiene su propio flujo de integración y despliegue continuo sin involucrar al resto, pudiendo desplegar a cualquier entorno de forma aislada, garantizando la estabilidad del resto de las partes de la aplicación.
  • Mayor velocidad en el desarrollo de la parte de la interfaz en la que estemos involucrados, ya que no tendremos la necesidad de buscar o reutilizar código perteneciente a otras áreas.
  • Facilidad para las pruebas: siempre nos será más fácil hacer testing sobre funcionalidades individuales concretas que sobre varias partes con sus posibles dependencias entre sí.
  • Utilización de varios frameworks: al tratarse de aplicaciones independientes dentro de un único contenedor, podemos emplear el framework que mejor se adapte a cada equipo de desarrollo. Esto podría agilizar la incorporación de nuevos equipos en el caso de necesitar nuevas funcionalidades, evitando tener que formarles sobre la tecnología que estemos usando.
    Más adelante explicaremos cómo esta característica puede ponerse en nuestra contra y por qué en muchos casos no se considera una ventaja sino lo contrario.

Ejemplo paso a paso

Imaginemos una aplicación web que tiene por objetivo la venta de zapatillas multimarca. A priori, la idea está bastante clara por las personas implicadas, todos ellos son buenos desarrolladores, pero carecen de experiencia implementando arquitecturas de microfrontends.

Por este motivo, deciden empezar realizando una prueba de concepto que reúna lo mínimo necesario para poder comprar una zapatilla de manera ágil y sencilla, con el único requisito de que los elementos de la página estén organizados en distintos microfrontends clasificados por funcionalidad.

Teniendo esto en cuenta, plantean la necesidad de tener 3 microfrontends como mínimo para poder realizar un primer proceso de compra sencillo, todos ellos conviviendo en la misma homepage, ya que por el momento no se quiere que la aplicación tenga secciones o un menú para evitar gestionar rutas y simplificar al máximo.

En un prototipo sencillo llegan al siguiente acuerdo:

Prototipo con opción de búsqueda, listado y carrito.
  • La parte superior de la homepage hará referencia al microfrontend dedicado a la búsqueda y filtrado de zapatillas, incluyendo todos los componentes necesarios para que el usuario pueda buscar a mano por un modelo concreto o usar directamente cualquiera de los filtros disponibles para ello.
  • La parte inferior de la homepage lo ocupará el segundo microfrontend dedicado a representar los resultados de la búsqueda, mostrando en forma de miniaturas el listado de zapatillas disponibles para añadir al carrito de compra.
  • La parte lateral derecha de la homepage estará reservada para el tercer microfrontend dedicado al carrito de la compra. En él se mostrará el listado de zapatillas que el usuario haya seleccionado con el fin de comprarlas.
  • Todo ello se mostrará en su conjunto en un último microfrontend que ejerce de contenedor para el resto y es al que accederán los interesados para ver nuestra prueba de concepto.

Para una primera aproximación de la prueba de concepto, podemos asegurar que hay suficiente complejidad en la homepage como para repartir cada microfrontend a un pequeño equipo de desarrolladores dedicado exclusivamente a esa funcionalidad específica.

Cada microfrontend podrá ser desarrollado, probado y desplegado por cada equipo con total autonomía, agilizando así la puesta en marcha de una primera demo que poder enseñar a los interesados.

Esquema con los tres microfonrtes: búsqueda, listado y carrito

De este modo, cada equipo podrá trabajar únicamente en su microfrontend, ejecutado y desplegado de forma individual, sin preocuparse por los conflictos o la coordinación con otros equipos paralelos. Sin embargo, de cara a nuestro cliente o persona interesada, esto resultará completamente transparente, ya que el producto final es un sitio web completo sin modificación alguna: el microfrontend contenedor (el host).

Con esta manera de organizarse y gestionar sus propios desarrollos, los equipos pasan a adoptar una organización vertical, un patrón muy común en este tipo de arquitectura mediante el cual cada equipo asume la responsabilidad de crear su respectiva parte de la interfaz y de tener en cuenta el resto de capas técnicas involucradas para hacerlo posible.

Todo ello sobre la base de un objetivo común del equipo y directamente relacionado con su funcionalidad específica dentro de la homepage.

Lo tres equipos se organizan de manera vertical

Con estos objetivos individuales, cada equipo irá más allá en el desarrollo de las partes de la aplicación y no se centrarán únicamente en escribir las líneas de código necesarias para su funcionamiento. Adoptarán una visión global en cada microfrontend como si se tratara de una aplicación completa, teniendo en cuenta otros aspectos relevantes tales como: presentar una interfaz con buen rendimiento, tener en cuenta la accesibilidad, proporcionar rapidez en la carga de los contenidos, mantener consistencia en los estilos, etc.

Con el prototipo como referencia y teniendo claro la distribución de las funcionalidades, se ponen manos a la obra y despliegan una primera versión de la prueba de concepto de la tienda de zapatillas:

Primera versión de la prueba de concepto

En la siguiente imagen podemos distinguir el espacio reservado para cada uno de los microfrontends de la homepage de la misma manera que se representaba en el prototipo. Los distintos equipos trabajarán en cada área de manera independiente para ofrecer una buena escalabilidad a futuro y agilidad en el desarrollo global de la aplicación.

Cada área sombreada de manera independiente.

Detalles técnicos

Para implementar este tipo de arquitectura, se ha optado por utilizar la técnica más extendida y utilizada hoy día: el plugin Module Federation que nos ofrece Webpack en su versión 5. Para componer la interfaz, hemos usado React como librería única para todos los microfrontends.

El enfoque principal de Module Federation es la creación de módulos remotos, entendidos como piezas de código independientes y compartidas entre diferentes aplicaciones. Cada aplicación puede cargar estos módulos remotos de forma dinámica durante el tiempo de ejecución, lo que permite compartir funcionalidades y recursos entre ellas.

Para implementar este plugin se llevan a cabo las distintas configuraciones y conceptos a tener en cuenta:

  1. Configuración de los módulos remotos: definición de aquellos módulos o partes de la interfaz que deben ser expuestas para ser utilizados por otros.
    En nuestro caso, todas las partes de la interfaz definidas en el prototipo son distintas aplicaciones remotas que se exponen para ser utilizadas: búsqueda y filtrado de productos, listado de productos y carrito de la compra.
    En la propiedad exposes indicamos los componentes que queremos exponer del microfrontend en el que nos encontramos para poder ser utilizado por otros.
Ejemplo de configuración del microfrontend de búsqueda de productos.
Ejemplo de configuración del microfrontend de búsqueda de productos.
  1. Configuración del módulo contenedor (host): definición del microfrontend que va a contener al resto de módulos remotos.
    En nuestro caso, el módulo contenedor será el microfrontend dedicado a la homepage, que importará el resto de microfrontends de cada funcionalidad y únicamente expondrá una store para la gestión del estado global de los microfrontends.
Ejemplo de configuración del microfrontend de homepage.
Ejemplo de configuración del microfrontend de homepage.

De esta manera, podemos importar todos los microfrontends y los componentes que hayamos expuesto desde ellos, como si de un módulo se tratara, tal y como los hayamos nombrado en nuestra configuración:

Importamos todos los microfrontends
  1. Configuración de librerías o dependencias compartidas: definición de aquellas dependencias o librerías que queramos compartir entre módulos, incluidas en la propiedad shared de la configuración.

En nuestro ejemplo, todos los microfrontends indican que quieren compartir la librería de React para hacer posible la comunicación entre ellos. En próximas versiones, podríamos necesitar la inclusión de nuevas dependencias más específicas. Por ejemplo, en el microfrontend dedicado al carrito de la compra, la librería Stripe para poder procesar los pagos de las zapatillas.

Los módulos federados de Webpack nos facilitan el poder compartir información y recursos entre aplicaciones independientes, mejorando la modularidad, la reutilización de código y la flexibilidad en el desarrollo de aplicaciones web.

Con unas pocas líneas de código en cada uno de los ficheros de configuración de Webpack, ya tendremos fácilmente implementada nuestra arquitectura de microfrontends.

Desventajas

  • Complejidad en la integración: al tratarse de muchas aplicaciones independientes, se requiere una mayor coordinación y gestión de la interacción que pueda surgir entre ellas. Esto puede provocar una mayor complejidad en su integración y la necesidad de contar con una capa de integración sólida y bien diseñada.
  • Gestión global del estado: al descomponer la interfaz de usuario en varias aplicaciones, nos encontramos con una mayor necesidad de gestionar el estado global de la aplicación de forma adecuada para garantizar la consistencia y la coherencia de la interfaz.
  • Mayor complejidad operacional: dado que cada microfrontend debe desplegarse de forma independiente, tendremos que gestionar un mayor número de entornos de desarrollo y servidores con sus respectivos costes asociados.
  • Aumento en el tamaño del bundle: la aplicación contenedora de todos los microfrontends puede verse afectada por un aumento en el tamaño del bundle debido a duplicidades en las dependencias. Este problema podría evitarse utilizando un monorepositorio en el que tuviéramos una librería de dependencias comunes.
  • Posibles problemas de rendimiento debido a una mayor cantidad de peticiones HTTP por la necesidad de cargar múltiples recursos en paralelo. Este problema puede ser evitado empleando técnicas de lazy loading y/o cacheando los recursos.

Buenas prácticas y aspectos a tener en cuenta

Vistas las ventajas y desventajas de esta arquitectura, vamos a enumerar un listado de buenas prácticas o aspectos a tener en cuenta antes de lanzarnos a su implementación en nuestro equipo:

  1. Limitar la libertad de cada microfrontend: evitar caer en el libertinaje de que los equipos puedan usar sus propios frameworks o librerías (Angular, Vue, React, LitElement, Svelte…). Sobre el papel resulta atractivo este nivel de libertad, pero en la práctica nos puede provocar muchos problemas, puesto que cada microfrontend requerirá sus propias dependencias para ejecutarse.
    El uso de una misma tecnología en todos los microfrontends nos ayudará a optimizar el tamaño final de nuestra aplicación, así como a evitar que aspectos relevantes (como el rendimiento y la experiencia de usuario) no se vean afectados por la ejecución de distintas tecnologías en paralelo.
  2. Valorar la necesidad de implementar microfrontends: tener claro si esta arquitectura realmente nos puede resultar útil en función de los equipos que queramos definir y sus objetivos o, por el contrario, nos basta con componentizar la interfaz sin necesidad de separar en microfrontends.
    Es importante pensar en esta arquitectura como una ayuda en nuestra manera de organizar los equipos y en sus objetivos individuales, teniendo en cuenta los despliegues independientes que nos ofrece, no tanto en separar únicamente la interfaz en distintos bloques dado que esto ya nos lo pueden ofrecer los componentes por sí solos.
  3. Pensar en la performance: los microfrontends indirectamente pueden ser nuestro mejor aliado para tratar de obtener un buen rendimiento en nuestra aplicación debido al code splitting o la distribución del código que hace cada microfrontend en varios bloques de código más pequeños.
    Gracias a ello, en función de cómo orientemos nuestra aplicación (todos los microfrontends en una misma homepage, un microfrontend por cada ruta de la app, un microfrontend por bloque de funcionalidad…) podremos cargarlos bajo demanda, evitando al navegador procesar bloques de código que aún no sean necesarios para el usuario en ese momento.
Bloques de código más pequeño
  1. Cuidado con la reusabilidad: el motivo de que decidamos implementar una aplicación con microfrontends no debe ser exclusivamente por buscar la reusabilidad de los elementos. Esto puede suponer un problema en el futuro, puesto que ante una incidencia o bug deberíamos tocar el código en varios microfrontends, provocando la coordinación con el despliegue de otro equipo y, por tanto, rompiendo con la independencia entre ellos.
    Todos conocemos la importancia de reutilizar bloques de código en el software para evitar duplicidades, pero en el caso de los microfrontends debemos evitar desarrollar funcionalidades pensando desde el principio en su posible reutilización dado que en la mayoría de los microfrontends difícilmente vamos a reutilizar su funcionalidad por ser tan específica a cada dominio del negocio. (p.e: listado de productos, carrito de la compra, etc)
  2. Evitar que nuestros microfrontends afecten a la estandarización y aspecto de nuestra página: es fundamental definir e implementar los distintos microfrontends a partir de un sistema de diseño, librería de componentes o simplemente una identidad propia bien definida para mantener la consistencia entre todos los elementos de la interfaz y evitar frankensteins_ que rompan con la estética de nuestra aplicación.
  3. Comunicación entre microfrontends: es importante definir cómo vamos a querer que nuestros microfrontends se comuniquen entre sí. ¿Vamos a necesitar hacer uso de un estado global almacenado de la aplicación o nos basta con la propagación de eventos mediante la propia API del navegador?
    Emplear librerías de terceros para este fin como por ejemplo Redux, Zustand, MobX o el propio Context API de React choca directamente con el principio de independencia de los microfrontends. La comunicación entre los microfrontends debería ser mínima, por el contrario, dos o más microfrontends que se comunican con mucha frecuencia quizá sea motivo de que deban ser una misma entidad y no varias independientes.
  4. Evitar el uso de microfrontends en proyectos pequeños o en los que la escalabilidad a futuro no sea una prioridad, como por ejemplo: un blog personal, un portafolio para mostrar nuestros proyectos, una prueba de concepto, etc.
    Si tu aplicación o equipo ha crecido notablemente en los últimos meses, como podría ser en el caso en una startup, siempre podréis valorar el paso a microfrontends separando poco a poco la interfaz en los distintos dominios del negocio.

Conclusiones

La arquitectura de microfrontends puede resultar muy buena opción en aplicaciones de gran tamaño o en las que hay muchos equipos de desarrollo involucrados. La implementación de este tipo de arquitectura trae consigo un conjunto de buenas prácticas implícitas para mejorar nuestra forma de trabajar, como es el desarrollo de forma modularizada e independiente o la gestión de los equipos de manera vertical, siempre con el foco en una buena escalabilidad para evitar elevados costes que puedan surgir al crecer como equipo y producto.

Son varias las empresas que actualmente han optado por esta arquitectura en algunos de sus productos, por lo que podemos afirmar que su aplicación tiene verdadera cabida en el mercado actual.

La prueba de concepto realizada para escribir este artículo se encuentra disponible en el siguiente repositorio.

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.