Introducción al patrón BLoC

En anteriores artículos hemos visto en qué consiste el estado compartido entre componentes en frameworks declarativos.

También hemos introducido una primera estrategia simple de gestión de estado compartido entre componentes, que consistía el levantar el estado al componente superior y hacer uso de la perforación de propiedades.

Posteriormente vimos como aplicar esta estrategia simple en Flutter y ReactJs.

En este artículo vamos a ver una estrategia un poco más avanzada utilizando el patrón BLoC.

Presentación del patrón BLoC

El Patrón BLoC fue diseñado por Paolo Soares y Cong Hu de Google, y fue presentado en la Dart Conference 2018. Ver Video.

BLoC significa Business Logic Component.

El objetivo con el equipo de Google diseño este patrón, fue la reutilización de código entre sus aplicaciones mobile, utilizando Flutter con Dart, y web, utilizando Angular Dart.

En esta presentación de la Dart Conference 2018 Paolo Soares presentó el patrón pero es una implementación concreta para Dart que aplicaron en Flutter y Angular Dart.

Sin embargo, el concepto del patrón puede ser más abstracto y amplio. Por lo tanto es agnóstico y puede ser utilizado en cualquier tecnología como ocurre por ejemplo con Redux, que es una implementación de Flux y existen multitud de librerías en diferentes tecnologías.

Mi intención en este artículo es presentar los conceptos desde un punto de vista más abstracto válido para cualquier tecnología.

Introduciendo el Patrón BLoC

Un BloC es un componente intermediario entre las vistas y nuestro modelo, como puede ser el presenter cuando utilizamos MVP o el view model al utilizar MVVM.

El modelo es un concepto bastante amplio dependiendo del tamaño y arquitectura de tu aplicación. Puede ser desde nuestra capa de datos en una arquitectura simplificada hasta nuestro dominio o hexágono si utilizamos Clan Architecture o Hexagonal Architecture.

Al igual que Redux, BloC se basa en conceptos de programación reactiva utiliando el patrón observer pero es bastante más simple y por lo tanto, más versátil.

El patrón BLoC inicialmente, como lo presentó Google, tiene varios objetivos:

  • Centralizar la lógica de negocio
  • Centralizar cambios de estado
  • Mapear al formato que necesita la vista

Por cada vista (componente o widget) o concepto lo suficientemente importante vamos a tener su correspondiente BloC.

Por ejemplo la vista (componente o widget) que representa a una página o que representa estado que es compartido por más de una vista, serían ejemplos donde tiene sentido crear un BLoC.

Centralizar la lógica de negocio

Google presentó este patrón en el contexto de tener aplicaciones sin una arquitectura definida, con componentes muy grandes, con lógica de negocio y donde se realizan también llamadas a una API dentro de los componentes.

La idea es extraer todo lo que no es renderización de vista como la lógica de negocio de los componentes del framework de UI en unas clases llamadas BLoC, ajenas a cualquier técnología o librería.

Estas clases no pueden tener ninguna dependencía de una librería concreta, dependen de abstracciones y estas deben ser inyectadas.

De esta forma utilizando el principio de inversión de dependencia, una clase BLoC solo debe contener lógica y puede escalar mejor a futuros cambios o a utilizarse en diferentes tecnologías.

Centralizar cambios de estado

Estas clases BloC van a centralizar los cambios de estado que se producen en la aplicación.

De esta forma estas clases BLoC serían las encargados de recibir las acciones o eventos que se producen en la aplicación, y que modifican el estado.

Mantienen cacheado el estado en memoria y también son las responsables de comunicar a todas las partes implicadas (componentes o widgets) los cambios de estado, ya que este debe ser observable.

Si recordáis lo que vimos en artículos anteriores sobre gestión simple de estado, donde el objetivo de levantar el estado al componente superior y utilizar la perforacion de propiedades era poder comunicar cambios de estado compartidos a varios componentes o widgets.

Una de las limitaciónes de esta estrategia era que cuando cambiaba el estado del componente superior, se renderizaban todos los hijos descendientes aunque no utilizarán el estado del componente superior para renderizarse.

Con el patrón BLoC se resuelve este problema, porque solo se renderizan los componentes o widgets suscritos a cambios de estado del BLoC.

Mapear al formato que necesita la vista

Los BLoCs también se van a encargar de formatear los datos a como lo necesitan las vistas, de esta forma esta lógica de presentación de formateo es también reutilizable.

Flujo de datos unidireccional

En patrones como MVVM la comunicación entre la vista y el view model tanto para representar el estado como el cambio del mismo se produce mediante data binding bidireccional.

mvvm

El problema de este enfoque es que resulta muy difícil depurar errores y mantener aplicaciones complejas porque se producen rebotes encadenados entre diferentes vistas y view models.

Al igual que ocurre en Redux, BloC se basa en un flujo de datos unidireccional.

bloc-unidirectional-data-flow

El binding entre la vista y el estado es solo en una dirección, leyendo el estado desde la vista para renderizar.

Para modificar el estado, se hace explícitamente, primero manejando eventos en el componente o widget y después invocando acciones o eventos en el bloc que modifican el estado en el BLoc.

Si os fijáis en el diagrama, el patrón BloC no entra en detalles en lo que ocurre dentro del BloC que es visto como una caja negra.

BLoC como una caja negra

Un BLoC visto como una caja negra es un componente que recibe eventos o actiones como inputs y tiene un estado observable o varios como ouput.

business-logic-component

Posibles implementaciones

Hemos visto la idea más abstracta del patrón y a partir de aquí se abre un abanico de posibles implementaciones.

Es importante diferenciar el patrón de sus implementaciones concretas, para poder adoptar posteriormente la que mejor nos encaja para nuestro proyecto.

Varias entradas y varias salidas

bloc-many-input-many-output

En la presentación de Paolo Soares, la implementación que utilizó tenía varias entradas y varias salidas.

De esta forma tendríamos un método por cada acción posible a realizar o si lo queremos hacer todo reactivo podemos tener un observer por cada entrada, esto es secundario.

Tanto las entradas como las salidas reactivas es la implementación que presentó Paolo Soares y puedes ver en el video.

En este escenario el tipo de las salidas queda abierto a tus necesidades, varias clases estado que encapsulan modelos de presentación, valores primitivos etc..

Una entrada y una salida

bloc-one-input-one-output

Existe la posibilidad de reducir las entradas y las salidas del BLoC a uno.

De esta forma necesitaríamos una jerarquía de eventos o acciones que relacione las posibles acciones que existen para una vista.

Tendríamos un método por ejemplo dispatch en el BloC y que recibe el evento o acción a realizar por parámetro y en este o en otro método debemos tener un switch que decide lo que hay que hacer en función del argumento.

También tendríamos una única salida, teniendo así que tener una clase estado que representa todo el estado de la vista (datos, barra de progreso visible o no, mensajes de error etc..) y es necesario un mapeo.

Estado inmutable

Los frameworks declarativos se basan en la inmutabilidad, los componentes cuando cambia el estado se reconstruyen completamente y si utilizamos estado inmutable se obtiene un mayor rendimiento.

Esto es así porque es más rápido saber si dos objetos que representan estado son diferentes si son una instancia distinta que comparando todas sus propiedades, hijos etc...

Por lo tanto el patrón BLoC es recomentable utilizarlo también con estado inmutable porque así favorecemos el rendimiento y eliminamos la complejidad a la hora de comparar estados.

Concepto versátil

Lo que me gusta del patrón BLoC es que es una idea bastante simple con un alcance corto y por lo tanto versátil, de forma que es perfectamente válida en aplicaciones sencillas y compatible con Clean Architecture o Hexagonal Architecture para aplicaciones más complejas.

En Redux por el contrario, tiene un alcance más amplio y esta pensada para ser la arquitectura completa de tu aplicación y encaja peor Clean Architecture o Hexagonal Architecture porque se solapan bastante en cuanto a responsabilidades. Y para aplicaciones sencillas puede ser excesiva.

En aplicaciones simples donde no hay mucha lógica de negocio, tener los BloCs con las responsabilidades que hemos visto puede estar bien.

Sin embargo en aplicaciones más complejas pueden ser demasiadas responsabilidades para estas clases haciendo que sean grandes complicando su mantenimiento.

En estos casos complementar el patrón BLoC con Clean Architecture o Hexagonal Architecture es una buena opción.

Al complementar el patrón BLoc con una arquitectura de las mencionadas, algunas de las responsabilidades del BLoC serían asumidas por otros componentes como los casos de uso o interceptores o servicios de aplicación.

En este escenario el patrón BLoC se queda como un patrón de presentación dónde su principal responsabilidad es la lógica de presentación como ocurre con MVC, MVVM o MVP cuando se utilizan con Clean Architecture.

Cuándo utilizar el patrón BLoC

Es un patrón que tiene conceptos en común con otros patrones como MVP o MVVM.

No existe ninguna restricción en cuanto a frameworks de UI.

Puede utilizarse tanto en frameworks imperativos como declarativos pero por su capacidad reactiva, encaja mucho mejor en entornos declarativos donde además hay una necesidad de compartir estado entre diferentes vistas.

Algunos frameworks o librerías donde encaja bien el patrón BLoc son:
ReactJS, Angular,Flutter, VueJS, Android Jetpack Compose o SwiftUI.

Artículos y cursos relacionados

Conclusiones

En este artículo hemos visto una introducción al patrón BLoC.

Personalmente me siento más cómodo utilizando el patrón BLoC que Redux porque que encaja mucho mejor con Clean Architecture, que es la arquitectura que suelo utilizar.

En futuros artículos veremos como puede se puede complementar este patrón con Clean Architecture, ejemplos aplicados a Flutter y ReactJS.