¿Por qué los componentes de React no deberían tener lógica de negocio?

6 min
¿Por qué los componentes de React no deberían tener lógica de negocio?

Uno de los problemas a los que menos empeño se le ha dado en React es la separación de la lógica de negocio de los componentes dentro de una aplicación. Una primera aproximación que se dio para solucionar esto fueron los High Order Components o HOC, componentes que albergaban lógica funcional, dejando al resto de componentes lo más functionless posibles para maximizar su reutilización. Esto tenía mucho sentido con los class components, mucho antes de que llegasen los functional components que hoy promueven desde el equipo de React y que se han convertido en un estándar a la hora de crear nuevos componentes.

La UI como un componente de infraestructura más

Cuando hablamos de desarrollo backend, sí que sabemos diferenciar los diferentes componentes de infraestructura a los que se conecta nuestra aplicación: el MySQL, el cluster de Redis, la REST API, … Para mí, un componente de infraestructura es todo aquello ajeno a nuestra lógica de negocio que interactúa con ella, ya sea una base de datos, una API o una interfaz de usuario.

Las interfaces de usuario, tienen muchas semejanzas a una REST API, es la forma que tienen los usuarios de comunicarse con la lógica del frontal, que a su vez se comunicará con el servicio a través de REST, GraphQL o el protocolo que utilicen. De esta manera, si seguimos esta razonamiento, podemos decir que un frontal que tenga una única comunicación con el backend a través de REST tiene dos componentes de infraestructura: el REST y la UI.

Cuando empiezas a ver la interfaz como parte de la infraestructura, se desarrolla código mucho más desacoplado del framework que se esté utilizando en ese momento. Hoy en tu empresa utilizan React, pero si el día de mañana se quieren cambiar a Vue, los cambios que se deberían realizar no deberían afectar a la lógica de negocio que tenga el frontal. Y es que sí, aunque deleguemos la mayor parte de la lógica funcional al backend, el frontend también tiene lógica de negocio que debe ser independiente del framework o de los diseños de Figma que nos pasen desde el equipo de UX.

Y ya no solo hablamos de cambios en el framework -que ya sabemos que no pasa todos los días- un rediseño de la aplicación puede ser un incordio si tenemos todos los componentes acoplados a la lógica funcional. Si a todo esto le unimos un código con tests que no hacen otra cosa que aumentar el coverage y con una estructura de carpetas nada clara, hacer grandes cambios en la aplicación se convierte en un verdadero incordio.

Llevando conceptos de SOLID y hexagonal al frontal

A todos nos han enseñado los conceptos SOLID, y la gran mayoría de los casos aplicados a lenguajes orientados a objetos. Pero basta con pararse a leerlos y entenderlos para darse cuenta de que estos principios tienen sentido más allá de Java, Scala o PHP.

Lo mismo pasa con la arquitectura hexagonal, o cualquier otra arquitectura limpia que queramos utilizar. No podemos acoplar nuestra aplicación la interfaz de usuario que estamos usando ahora o a la aplicación REST con la que nos estamos comunicando con nuestro servicio backend.

La mayoría de ocasiones terminamos por tomar como modelo de dominio de nuestro frontal el modelo de la interfaz que tenemos con nuestro backend. Si el backend nos devuelve un usuario de la siguiente forma…

{
  name: "Juan",
  email: "juan@juanoa.com"
}

… y propagamos este modelo a través de toda la aplicación, si por algún motivo cambia el modelo de la interfaz, estamos realmente jodidos. En desarrollo de software, los contratos que se definen a la hora de crear una nueva interfaz deben de ser fácilmente sustituibles. Si por alguna razón cambiar el modelo de la interfaz supone hacer grandes cambios en el código de alguno de los afectados, es que algo estamos haciendo mal.

En frontal tenemos que tener un dominio, que por lo general se le dará un aire al que tenemos en backend. Al fin y al cabo lo que este dominio representa es el negocio que respalda a nuestra aplicación y el negocio es el mismo tanto para el backend como para el frontend.

De esta forma podemos utilizar una arquitectura limpia para convertir los datos y las acciones que nos llegan desde la infraestructura en entidades de dominio sobre los que se puede ejecutar la lógica de negocio.

Si seguimos el razonamiento de que la interfaz de usuario es también parte de la infraestructura, también tenemos que crear un modelo de datos o UI-DTOs que serán lo que usará nuestra interfaz (que de nuevo recalco, puede cambiar con independencia de la lógica funcional) y un port/adapter con el que se comunicará con nuestra capa de aplicación.

¿Pero, realmente necesitamos un dominio y una capa de aplicación en frontal? A continuación te explico por qué yo creo que sí es necesario.

Lógica de negocio en frontal

Imaginémonos que estamos desarrollando la aplicación de banca online de un gran banco. Nos disponemos a crear una nueva página que permita a los clientes pedir un préstamo online de forma sencilla, sin la necesidad de pasar por la oficina. Está claro que hay lógica que hay lógica que no deberíamos tratar desde el frontal, como por ejemplo, si un cliente está en alguna lista de morosos o cuál es el máximo de crédito que se le puede conceder. Sin embargo, si observamos el siguiente (y simple) diseño:

diagrama-1.png

Como clientes, esperamos que cada vez que cambia el tiempo máximo de devolución del crédito, tenga al instante el nuevo resumen de la operación. Y esto tiene todavía más sentido con el slider de selección: quiero ver en tiempo real cómo se modifica el resumen a la vez que voy desplazando el slider.

Eso es lógica de negocio y si cambiamos el slider por un input numérico y el selector de mensualidad por un grupo de radio buttons, la lógica que calcula el resumen es la misma, por lo tanto, debe de quedar desacoplada del componente, ya que este puede cambiar. Una vez visto esto es mucho más sencillo encontrar la relación con la arquitectura hexagonal que llevamos usando con éxito tantos años en frontal: si la tecnología de la base de datos cambia, solo deberíamos reprogramar esa parte por que la capa funcional no está acoplada a la infraestructura.

La UI como un componente de infraestructura, desacoplada de la lógica funcional. Un cambio en el diseño no debería provocar cambios en los ficheros o en las funciones de la capa de aplicación o dominio.

Ports & Adapters. ¿Tienen sentido?

Una cosa es separar la parte funcional de la interfaz, con la que se gana agilidad programando y un código mucho más resiliente, y otra cosa es realizar sobre ingeniería. ¿Son necearios los port/adapters/clientes/drivers o como los quieras llamar? Yo creo que sí. ¿Tienen que encontrarse en ficheros independientes como pasa en OOP? Mi opinión es que no hace falta.

Pensemos en el caso anterior de la página de solicitud de un préstamo. Si cambiamos el diseño como mentamos anteriormente, el input de nuestro port pasaría de ser…

{ sliderInput: SliderInput, optionInput: OptionInput }

a …

{ numberInput: NumberInput, radioInput: RadioInput }

Esta lógica está muy acoplada al componente que estamos usando para que el usuario introduzca los valores del préstamo, así que, ¿por qué no meter esta lógica en el componente? Siempre que se utilice, se utilizará el mismo procedimiento para adaptar el negocio a la UI y viceversa.

El problema viene si hacemos cambio en el dominio. Si cambiamos la forma en la que nuestra aplicación entiende el concepto de Préstamo, estos adapters también tienen que cambiar. Si no son fácilmente localizables, todo el trabajo que hemos realizado para desacoplar se irá al garete. Utilizar TS es una buena opción para evitarnos errores en producción, pero no todos los proyectos usan esta tecnología. Separar los componentes por caso de uso se está volviendo cada vez más usual, pero estaríamos acoplando la estructura de carpetas de la UI a la capa de aplicación. ¿Un cambio en los casos de uso debería provocar que los componentes cambien de carpeta? Yo creo que no. Para estructurar componentes ya tenemos otros sistemas como Atomic Design, más centrados en lo que creo que tiene sentido: su modularidad.

Entonces, ¿cómo estructurar el proyecto? Bueno, creo que esto es algo que se merece otro post…