lunes, 27 de enero de 2020

Capítulo 27: Grande y pequeño - parte 2 - Clean Architecture


El problema kitty

Como un ejemplo de estas dos falacias, veremos nuestro sistema de agregador de taxi de nuevo. Recuerda, el sistema sabe cuántos proveedores de taxi hay en una ciudad dada y permite a los clientes pedir carreras. Se asume que los clientes seleccionan los taxis basados en un número de criterios como el tiempo de recogida, el coste, el lujo y la experiencia del conductor.

Se quiere que el sistema sea escalable así que nosotros elegimos construir este sistema a partir de muchos microservicios. Subdividimos a nuestro personal de desarrollo en muchos equipos pequeños, cada uno de los cuales es responsable de desarrollar, mantener y operar un número correspondientemente pequeño de servicios.

El siguiente diagrama muestra cómo nuestros arquitectos ficticios ordenaron los servicios para implementar esta aplicación. El servicio «TaxiUI» trata con el cliente, quién usa el dispositivo móvil para pedir taxis. El servicio «TaxiFinder» examina los inventarios de varios «TaxiSuppliers» y determina que taxis son los posibles candidatos para el usuario. Estos se depositan en un registro de datos a corto plazo adjunto a ese usuario. El servicio «TaxiSelector» toma los criterios del usuario y elige un taxi apropiado entre los candidatos. Esto saca al taxi fuera del servicio «TaxiDispatcher» el cual ordena el taxi apropiado.

Ahora supongamos que este sistema ha estado operativo desde hace más de un año. Nuestro equipo de desarrollo ha estado desarrollando alegremente nuevas características mientras mantenía y operaba todos estos servicios. 

Un día el departamento de marketing mantiene una reunión con el equipo de desarrollo. En esta reunión, ellos anuncian sus planes para ofrecer un servicio de entrega de gatitos a la ciudad. Los usuarios pueden ordenar que los gatitos sean entregados en sus hogares o en sus lugares de negocios.

La compañía establecerá varios puntos de recogida de gatitos a través de la ciudad. Cuando se hace un pedido de un gatito, se seleccionará el taxi más cercano para recoger al gatito de uno de aquellos puntos de recogida y entonces lo entregará en la dirección apropiada.

Uno de los proveedores de taxi está de acuerdo en participar, otros probablemente lo sigan pero otros puede declinar el acuerdo.

Cabe la posibilidad de que algunos conductores sean alérgicos a los gatos por lo que estos conductores no deberían ser seleccionados para este servicio. Los clientes también pueden ser alérgicos a los gatos por lo que un vehículo que ha estado en uso para entregar gatitos en los últimos tres días no se debería seleccionar para los clientes quienes hayan manifestado alergias.

Mirando al diagrama de servicios. ¿Cuantos servicios cambiarán para implementar esta funcionalidad? Pues todos ellos. Claramente, el desarrollo y despliegue de la funcionalidad gatito tendrá que estar coordinada cuidadosamente.

En otras palabras, los servicios está todos acoplados y no se pueden desarrollar, desplegar ni mantener independientemente. 

Este es el problema con las aspectos transversales. Cada sistema software debe encarar este problema, ya sea orientado el servicio o no. 

Las descomposiciones funcionales, del tipo representado en el diagrama de servicio en la Figura 27.1, son muy vulnerables a las nuevas características que atraviesan todos esos comportamientos funcionales.

Objetos al rescate

¿Cómo deberiamos haber solventado ese problema en una arquitectura basada en componentes? Tomando en consideración los principios de diseño SOLID nos habría impulsado a crear un conjunto de clases que podrían extenderse polimórficamente para manejar estas nuevas características .

El siguiente diagrama muestra la estrategia. Las clases en este diagrama corresponden aproximadamente a los servicios mostrados en la figura anterior. Sin embargo, hay que tener en cuenta los límites. Las dependencias siguen la regla de la dependencia. 


Mucha de la lógica de los servicios originales se preserva dentro de las clases base del objeto modelo. Sin embargo, ese proporción de la lógica que fue específica a las carreras se ha extraído de un componente «Rides». La nueva característica  para los gatitos se ha colocado en el componente «Kittens». Estos dos componente sobreescriben la clases abstractas base en el componente original usando un patrón como «Template Method» or «Strategy».

Hay que tener en cuenta que los dos nuevos componentes, «Rides» y «Kittens» siguen la regla de la dependencia. Tenga en cuenta que las clases que implementan aquellas características están creadas por factorías bajo el control de la UI.

Claramente, en este escenario, cuando la característica Kitty se implementa, «TaxiUI» debe cambiar pero no es necesario cambiar nada más. Más bien, un nuevo archivo jar, o gema o DDL se añade al sistema y dinámicamente se carga en tiempo de ejecución.

De esta manera, la característica Kitty se desacopla es desarrollable y desplegable  independientemente.

Servicios basados en componente

La cuestión más obvia es: ¿podemos hacer esto con servicios? Y la respuesta es ¡por supuesto! Los servicios no necesitan ser pequeños monolitos. Servicios pueden estar diseñados usando principios SOLID y tener una estructura de componentes de manera que los nuevos componentes se pueden añadir sin cambiar los componentes existentes en el servicio.

Piensa en un servicio en Java como un conjunto de clases abstractas en uno o más ficheros ja. Piensa en cada característica o ampliación de característica como otro fichero jar que contiene clases que extienden clases abstractas en los primeros archivos jar. Desplegar una nueva característica no implica redesplegar los servicios si no en una manera de simplemente añadir los nuevos ficheros jar a los paths de carga de aquellos servicios. En otras palabras, añadir nuevas características conforma al principio «Open-Closed».

El diagrama de servicio de la siguiente figura muestra la estructura. 


Los servicios todavía existen como antes pero cada uno tiene su propio diseño de componente interno permitiendo  a las nuevas características que sean añadidas como nuevas clases derivadas. Aquellas clases derivadas viven dentro de sus propios componentes

Preocupaciones transversales.

Lo que hemos aprendido es que los límites arquitectónicos no caen entre servicios. Por el contrario, aquellos límites se ejecutan a través de servicios dividiendo estos en componentes.

Para tratar con las preocupaciones transversales que todos los sistemas significativos encaran, los servicios se deben diseñar con arquitecturas de componentes internos que siguen la regla de la dependencia, como se muestra en el siguiente diagrama. 
Aquellos servicios no definen los límites arquitectónicos del sistema, en vez, los componentes dentro de los servicios lo hacen. 

Conclusión

Tan útiles como son los servicios para la escalabilidad y la desarrollabilidad de un sistema, ellos no son en sí mismos elementos arquitectónicos significativos. La arquitectura de un sistema se define por los límites dibujados en ese sistema y por las dependencias que cruzan aquellos límites. Esa arquitectura no está definida por los mecanismos físicos por los cuales los elementos se comunican y ejecutan.

Un servicio podría ser un único componente, rodeado completamente  por un límite arquitectónico. Alternativamente, un servicio podría estar compuesto de varios componentes separados por límites arquitectónicos. En casos raros, los clientes y los servicios pueden estar tan acoplados como para no tener ningún significado arquitectónico.


No hay comentarios:

Publicar un comentario