El patrón BFF: Backend for Frontend

Esta semana he estado leyendo sobre Backend For Frontend o BFF, un patrón de arquitectura de microservicios dio a conocer al mundo uno de los ingenieros de SoundCloud en el año 2015, aunque sigue aplicándose a día de hoy.
En este artículo veremos el problema que tenemos en un entorno de microservicios y cómo el patrón BFF es una de las soluciones posibles.

El problema

Una aplicación web presenta diferentes niveles de complejidad en función de la necesidad. El ejemplo más básico es el de un único sistema que genera el contenido de lado del servidor, donde tanto el frontend como el backend forman parte del mismo, lo que se considera un monolito (por ejemplo, este blog).

Este sistema se puede hacer más complicado si convertimos este monolito en un conjunto de APIs REST unido a un código más complicado del lado del cliente (una Single Page Application o SPA) (o un estéreo-liso). Hasta este momento tenemos una única conexión entre cliente y servidor.

La complejidad puede aumentar mucho más por dos lugares diferentes. Por una parte, podemos necesitar soportar otros canales, como una aplicación móvil, una skill de Alexa, o una API para terceros. Por otra, el backend se puede dividir en diferentes servicios, cada uno con una API específica, a la que nos tendremos que adaptar.

Llegados a este punto, nos encontramos en una posición en el que los clientes (la aplicación móvil, la aplicación principal, la skill de alexa y un desarrollador independiente) dependen de una API común o de una serie de APIs independientes, con lo cual se genera un acoplamiento fuerte entre dos o más sistemas.

El resultado es que todos los clientes acceden a APIs que dan mucha más información de la que ellos necesitan, y no tienen una manera específica de controlar qué información utilizar y cómo leerla. Además, esto limita la capacidad de los desarrolladores de la API, ya que de repente cualquier cambio tiene que ser consensuado con todos los clientes, aumentando la probabilidad de generar regresiones.

La filosofía

La filosofía propuesta por BFF intenta simplificar la relación entre un cliente y su servidor, generando un punto de entrada para cada cliente. Esto significa que un cliente accede a una API exclusiva que soporta casos de uso específicos de cada cliente y que el cliente tiene total libertad para adecuar su API a sus propias necesidades.

En un patrón de microservicios, esto significa generar un servicio intermedio que de soporte única y exclusivamente a este tipo de cliente. Este servicio, desde el punto de vista de pertenencia, le pertenece al equipo del cliente. Volviendo a nuestro ejemplo, el equipo de la Skill de Alexa tendrá una API diferente al equipo de la app móvil, que el de la aplicación de escritorio.

La ventaja principal, que he comentado anteriormente, es que se simplifica el acoplamiento entre una aplicación y sus servicios de backend. Además, es más fácil que un servicio de backend evolucione, ya que las adaptaciones no necesitarán desplegar nuevos cambios en cliente, sino solamente en los adaptadores.

El ejemplo

Supongamos que queremos diseñar la siguiente iteración de Pokemon Go, tenemos una aplicación móvil en la que capturaremos los Pokemon, una web donde podemos ver detalles de nuestras capturas y una Skill de Alexa en la cual podemos saber en qué posición estamos del ranking.

En un desarrollo tradicional con un único endpoint, tendríamos una API REST en el cual todos los clientes efectuarán las llamadas necesarias, todos los clientes utilizarán el mismo protocolo y tipo de datos.

Este sistema convierte nuestra API rest en un punto de fallo para tres sistemas diferentes, con el añadido de que, salvo el sistema web, un fallo que rompa los clientes de iOS y los de la Skill de Alexa pueden tardar en corregirse, ya que depende de las reglas de cada marketplace.

Por otro lado, ¿que pasaría si cada cliente tuviera un servicio asociado? Esto provocaría un aumento considerable en la libertad de los diferentes equipos en desarrollar soluciones en el servidor para sus problemas específicos.

  • El equipo que gestiona la web podría decidir en utilizar GraphQL para generar solamente una petición, y utilizar código isomórfico para tener JavaScript en cliente y servidor.
  • El equipo que gestiona la Skill de Alexa podría utilizar una opción servirles en Lambda utilizando Python.
  • El equipo que utiliza la aplicación iOS, con el objetivo de ahorrar ancho de banda a los clientes, puede decidir montar un servicio a bajo nivel con un socket TCP.

Este cambio no afecta a la lógica de negocio, ya que estos servidores seguirán llamando a nuestro servicio común, que a su vez puede evolucionar de manera separada dividiéndolo en otros tantos servicios.

Esto permite además una evolución mucho más rápida de los sistemas, ya que en caso de un cambio importante, solamente será necesario actualizar los servicios intermedios, y las aplicaciones cliente pueden continuar funcionando.

Los inconvenientes

El principal problema de utilizar una API para cada tipo de cliente es la complejidad, de repente tener un servicio y tres aplicaciones cliente se convierte en tener tres servicios, tres aplicaciones cliente y un cuarto servicio por encima de todas ellas. Esta complejidad no está exenta de coste, lo cual puede repercutir en más hardware, más configuración y encarece el desarrollo.

Otro problema de esta aproximación es que requiere que los equipos de cliente tengan capacidad para generar su propio servicio de backend, lo que implica tener conocimientos de backend en un equipo frontend o un equipo mobile, lo cual no siempre es el caso.

El dilema de dogfooding

Dogfooding (o “eat your own dog food”) es el término que en ocasiones empleamos para utilizar las mismas herramientas y APIs a las que pueden acceder nuestros clientes externos, idealmente con el objetivo de no crear ciudadanos de primera y de segunda.

Sin embargo el peligro que tiene una API pública es el riesgo de romper a clientes externos con cambios que tengan sentido de manera interna pero no de manera externa, así que podríamos considerar la API pública como una API más, con una evolución diferente.

La decisión, to bff or not to bff?

La respuesta es, como siempre, depende. Si tenemos un sistema en el cual un tipo de cliente solamente va a utilizar un subconjunto de la API principal, puede ser buena idea crear un acceso solamente para este tipo de clientes. Ejemplo: Una skill de Alexa para agregar elementos a una lista de tareas, y un portal web para administrarlas.

Si, por el contrario, todos nuestros clientes van a utilizar los mismos métodos de nuestra API, y todos los clientes van a ser exactamente iguales en cuanto a funcionalidad, es posible que no necesitemos más que una API para gobernarlas a todas.

Echando la vista atrás recuerdo un ejemplo muy claro en el que tener un servicio genérico con el objetivo de soportar varios tipos de cliente condicionó muchísimo el diseño del primer y único cliente que se terminó desarrollando, agregando un montón de lógica al cliente de manera innecesaria para adaptar los datos que venían del servicio. Esto se podría haber evitado considerando que el servicio iba a tener un único cliente.

Resumen

BFF es un patrón de diseño de microservicios que nos permite considerar que el acceso a nuestro servicio se realiza desde clientes específicos con necesidades específicas, permite evolucionar las diferentes capas de la aplicación de manera paralela y elimina acoplamiento entre clientes diferentes con casos de uso específicos, siempre y cuando podamos asumir los costes de tener diferentes puntos de entrada a nuestra aplicación mantenidos por diferentes equipos.

Si quieres leer más del tema te recomiendo que eches un vistazo a los siguientes artículos:

Creando diagramas de manera efectiva con el modelo C4

En nuestro día a día empleamos múltiples niveles de abstracción para referirnos a los sistemas de información que construimos, manipulamos o mantenemos, nos referimos a sistemas, aplicaciones, servicios, APIs, llamadas, funciones, almacenamiento, memoria, ficheros, eventos, etc.

Muchas de estas abstracciones las comentamos de manera informal, las describimos a través del código que escribimos y que ejecutamos, las describimos en prosa, o empleamos la herramienta favorita de los arquitectos y la más temida por los desarrolladores, los diagramas.

Un diagrama, ya sea de componentes, de secuencia, de interacción o de clases, no es más que otra abstracción en la que describimos como es o cómo pretendemos que sea nuestro sistema. El diagrama nos puede permitir pasar por ciertas capas de descubrimiento, descubrir o fijar los límites de nuestro servicio en comparación con otros, y es una herramienta que nos permite mantener una conversación.

Esta herramienta es crítica cuando tenemos que interactuar con otros equipos, o compañeros que pueden no tener todo el contexto. Es por ello que es importante tener en cuenta el público, el objetivo, y el nivel de abstracción que queremos mantener. al crear dichos diagramas y representar contextos

Una metodología para la representación de diagramas que he estado poniendo en práctica recientemente es el modelo C4 por Simon Brown, en el cual, siguiendo ciertas prácticas e iterando sobre nuestros diagramas, podemos diseñar nuestro sistema utilizando varios niveles de abstracción:

  • Contexto
  • Contenedor
  • Componente
  • Código

Pese a que el modelo define 4 capas, en este artículo hablaremos de las tres capas superiores, ya que la cuarta se asemeja bastante a diagramas a los que podamos estar más familiarizados, y en ocasiones no es necesario ni siquiera llegar a ellas.

El ejemplo

A la hora de escribir el artículo pensé en algo del estilo “lista de tareas” dada mi obsesión por las aplicaciones, metodologías y otros, pero al final me decanté por un sistema que me permitiera escribir artículos en un blog.

Los requisitos son los siguientes:

  • Un lector puede ver artículos publicados y escribir comentarios.
  • Un autor puede escribir artículos, editarlos y borrarlos
  • Un autor, además, recibirá una notificación cuando se cree un nuevo comentario.

Vayamos manos a la obra:

Nivel 0: Contexto

tbe-blog-page-3

A este nivel queremos definir de manera clara quienes son los actores que interactúan con nuestro sistema, qué sistemas tenemos, y una primera idea de cómo se relacionan entre ellos.

A este nivel podemos tener una conversación con un potencial usuario del sistema, o con nuestros compañeros de negocio, ya que los casos de uso generales se definen aquí.

Es también una oportunidad para destacar qué queda fuera del sistema, y hasta qué punto se pueden modificar esos sistemas. Podemos utilizar esquemas de colores para destacar qué sistemas podemos y cuales no podemos modificar.

Nivel 1: Contenedores

Tbe Blog-Page-4

En este caso hacemos zoom en cada uno de los sistemas, y lo separamos en contenedores que tienen una función lógica. En el caso de nuestro blog tenemos dos subsistemas diferentes, uno encargado de la edición de artículos y otro de la visualización, así como una API entre la que interactúan ambos sistemas.

A este nivel podemos tener conversaciones a nivel técnico con diferentes equipos involucrados en el proyecto, el nivel de abstracción es menor, y podemos tomar decisiones como cuantos subsistemas construimos y cómo se reparte el trabajo de los mismos.

Nivel 2: Componentes

Tbe Blog-Page-5

Este nivel es el más profundo al que llegaremos en este artículo, aquí podemos establecer una separación más clara, en el caso de la UI podemos separar las diferentes páginas que existirán, y en el caso de la API backend los diferentes componentes que podemos emplear, en el que cada componente puede ser una librería, un paquete, un proyecto o una unidad semántica de código.

En este nivel podemos tomar decisiones de arquitectura directamente a nivel de equipo, entender las diferentes relaciones entre los diferentes componentes y modularizar más, si fuese necesario.

Evolución

Una de las características más interesantes de este estilo de modelado es que es iterativo, una vez que empiezas y vas pasando por las diferentes capas descubres que estabas empleando el modelo erróneo de abstracción, y mueves “cajitas” entre las diferentes capas.

El primer diagrama que hagamos nunca será perfecto, pero el hecho de tener tres diagramas diferentes e iterar entre ellos nos permitirá descubrir nuestra arquitectura, encontrar límites y tener una mejor idea del alcance de los productos que vamos a construir.

Desde el punto de vista práctico, recomiendo utilizar una herramienta que nos permita tener los tres tipos de diagramas visibles a la vez, ya que nos veremos en la situación de estar cambiando componentes entre los diferentes niveles de abstracción.

Retrospección

Podemos utilizar estos diagramas no solamente como una manera de crear software, sino también de familiarizarnos con software existente. A la hora de enfrentarnos a un nuevo proyecto, entender el sistema en el que nos encontramos nos permite mitigar el factor ”Pulpo en garaje” y ser más productivos desde el principio.

Conclusiones

En general los diagramas son otra herramienta que podemos utilizar tanto para descubrir, explorar o enterarnos de donde nos hemos metido, no son el fin, ni mucho menos, sino un medio para construir mejores sistemas. Metodologías como C4 nos permiten crear mejores diagramas y con ello, mejores sistemas.

Más información sobre el modelo C4 en la web de Simon Brown: The C4 model for software architecture

Imagen por Kaleidico vía Unsplash

Principios SOLID e inyección de dependencias

El pasado jueves 27 tuve la oportunidad de asistir a la reunión mensual del grupo de usuarios .Net de Madrid (Mad.nug) y la conversación estuvo enfocada a la arquitectura de software, inyección de dependencias, inversión de control y algo de principios SOLID, algo de lo que era bastante ajeno.

Este tema de conversación me pareció bastante interesante y he estado indagando, lo que me ha llevado a escribir a modo de artículo, algunas ideas que he adquirido de estos temas.

En este artículo se verá un resumen los principios SOLID, luego se hablará de inyección de dependencias, y finalmente un poco de Unity, un framework de inyección de dependencias creado por el equipo de Microsoft Patterns and Practices.

Principios SOLID

Son principios de diseño básicos para poder crear aplicaciones robustas y sostenibles, creados por Robert C.Martin, y es, como define Hadi Hariri en el vídeo que se enlaza al final del artículo, un acrónimo de acrónimos, es decir, una combinación de varios principios ya existentes.

  • S-RP: Single Responsability Principle: Especifica que una clase (o un método) solamente tiene que tener un propósito concreto, es decir, tener una única responsabilidad o un motivo por el que cambiar. Esto se reduce en clases más pequeñas y más fáciles de entender.
  • O-CP: Open Closed Principle: Se basa en delegar la responsabilidad a la clase. Si existen una serie de acciones que dependen del subtipo de una clase, es más fácil que esa funcionalidad esté implítica en la clase, y que las subclases las reimplementen (o las especifiquen) mediante polimorfismo, una de las características más comunes de los lenguajes orientados a objetos. Esto se resume en que una clase debe estar abierta a extensión y cerrada a cambios.
  • L-SP: Liskov Susbtitution Principle: Explica que todas las clases que hereden de una superclase, por decirlo así, no deben replicar funcionalidad ya implementada en esta clase, con lo cual la clase base se deberá poder sustituir por las subclases en cualquier región del código.
  • I-SP: Interface Segregation Principle: Propone dividir las interfaces en algo que tenga más sentido, en vez de dejar operaciones de la interfaz por implementar, en cierta manera sería una aplicación del SRP a las interfaces, teneniendo en cuenta que una clase puede implementar varias interfaces de manera simultánea. No se debe forzar a clientes a implementar métodos no necesarios.
  • D-IP: Dependency Inversion Principle: Permite conseguir un desacoplamiento de las clases en el código, de tal manera que si una clase emplea otras clases, la inicialización de los objetos venga dada desde fuera. Además, si se puede sustituir las clases por interfaces, se puede extender a otro objeto que implemente la interfaz, sin variar el parámetro.

Estos cinco patrones componen SOLID, pero hay más, está la ley de Demeter, los los principios DRY… varias buenas prácticas para desarrollar buen software que van bastante más allá del tema de este artículo. Otro detalle importante es que son patrones, no metodologías, se pueden seguir o no, aunque las razones para seguirlos son bastante interesantes.

Inversión de dependencias e inyección de dependencias.

La inversión de dependencias, como se ha dicho en el apartado anterior, lleva implícita la inyección de dependencias. Para explicar este concepto, se puede mostrar el siguiente ejemplo, partiendo de este código inicial.

class MiClase
{
    public MiClase()
    {
        ClaseA a = new ClaseA();
        ClaseB b = new ClaseB();

        a.UnMetodo();
        b.OtroMetodo();
    }
}

Este código tiene un problema de acoplamiento, ya que la clase depende de otras dos clases. Si cambiase el comportamiento de las clases ClaseA o ClaseB, habría que cambiar el código de esta clase, no digamos si los constructores tienen parámetros.

Una primera mejora consistiría en pasar los objetos por argumento, de tal manera que sean definidos fuera de la clase, eliminando la dependencia en esta fase, e inyectándola mediante el constructor, por lo tanto, si el código de estos cambia, no será necesario editar esta clase, pero, nos aseguraremos que el código sigue siendo válido?:

class MiClase
{
    public MiClase(ClaseA a, ClaseB b)
    {
        a.UnMetodo();
        b.OtroMetodo();
    }
}
Una segunda mejora consistiría en sustituir los objetos por interfaces, de tal manera que lo que se pase al constructor de la función no sea una clase, sino un contrato, una implementación que tendrá que cumplir con la interfaz acordada, aunque el contenido de la clase cambie, o sea otra clase completamente diferente que implemente la interfaz:
class MiClase
{
    public MiClase(IClaseA a, IClaseB b)
    {
        a.UnMetodo();
        b.OtroMetodo();
    }
}
De esta manera si se crea una clase OtherClass que implemente IClaseA, se puede pasar por el constructor y el funcionamiento de MiClase no variará, con lo cual no será necesario editar el código. La consecuencia es que esta clase está más desacoplada, lo que permite, por ejemplo, poder pasar objetos de prueba para test unitarios, reducir los fallos y, en definitiva, tener un código más sólido.

Unity, un contenedor IoC

Un detalle del apartado anterior, es que el código no es del todo desacoplado, es decir, estará acoplado a la llamada al constructor o a la función, para terminar de resolver este «pequeño problema» surge Unity, que se define como un contenedor de inyección de dependencias.

El funcionamiento del contenedor permite primeramente registrar los tipos que se usarán, es decir, los objetos se devolverán al solicitar las diferentes interfaces. Una vez se han registrado, para crear los objetos solamente será necesario llamar al contenedor, y él se encargará de resolver la ruta pedida, es decir, emparejar la interfaz que se solicita con el objeto definido en la fase de registro.

Una ventaja de este contenedor es que la configuración se puede realizar vía XML, evitando el hecho de recompilar.

Resumen

Programar no es solamente escribir código, existen estilos y patrones que si los seguimos 1. No hacen daño y 2. Permiten obtener un código legible y mejorar su mantenibilidad. Espero que la lectura te haya resultado interesante, y si quieres más información, te remito a los libros y las charlas de Hadi.

Más información: