miércoles, 29 de mayo de 2013

Un típico tradeoff de arquitectura

Introducción

¿Alguna vez han necesitado tener dos instancias de una clase que se relacionan entre si en un contexto de negocio y las siguientes premisas para la realización del diseño: Baja complejidad de implementación, mínimo impacto en el código existente, mantenible en el tiempo lo que incluye fácil localización de los segmentos de código que tienen bugs, escalable y buen desempeño?.

Este fue el escenario que nos planteó tal reto de diseño: La arquitectura de referencia definía la ejecución de un proceso de negocio como una serie de pasos no relacionados entre sí (Lector - Completador - Validador). Todos ellos serían orquestados por un ente externo. El proceso consistía en Leer información de archivos Txt, completar la información leída con elementos de la base de datos de la compañía (Esta sería la información real de las entidades de dominio), y validar la información de modo que se pudiera continuar con un proceso de negocio subsiguiente o parar el procesamiento. Las validaciones deberían en algunos casos comparar los valores leídos con los valores completados. El problema radicaba en el hecho de tener un componente inicial (El lector) que leyera la información y un componente intermedio (El completador) que podría reemplazar la información ya leída. En este caso al componente validador solo le llegaría la información completada y no tendría forma de comparar con la información leída. A continuación una imagen que ilustra lo descrito.


Para dar solución a esta problemática se plantearon las siguientes alternativas de diseño:

Diseño 1. Replicar datos en el modelo de dominio

Normalmente cuando tenemos deficiencias en el diseño lo primero que hacemos es empezar a replicar atributos en los objetos de negocio o a utilizar las estructuras que ya tenemos al amaño del proceso de negocio puntual y no como referencia del negocio real.

Ventajas

  1. Escalable porque usa menos instancias de objetos en comparación a las siguientes soluciones
  2. Baja complejidad de implementación
  3. Se requiere pocas líneas de código para su implementación
  4. Al existir menor cantidad de objetos habrá menor uso del Garbage collector lo que beneficia en menor medida el desempeño.

Desventajas

  1. No es mantenible.
  2. Poco flexible.

En la imagen anterior vemos como se adiciona un nuevo atributo llamado "numeroCompletado" cuyo objetivo es que el completador coloque el número cargado de la base de datos y no reemplace el que puso el lector. De esta manera el validador podrá comparar estos dos valores de forma fácil.

El problema con este enfoque es que conforme avance la implementación, el modelo de dominio va a tener cada vez mas adaptaciones puntuales a cada proceso de negocio hasta volverse insostenible.

Diseño 2. Repositorio común a todos los componentes

La siguiente aproximación de diseño planteó tener un espacio de memoria que fuese accesible por todos los componentes (Lector - Completador - Validador), de esta manera cuando el lector reemplazase valores en los objetos de negocio, el validador podría acceder al repositorio y obtener el objeto que originalmente fue leído

Ventajas

  1. Incrementa la flexibilidad porque la complejidad del modelo de dominio no aumenta a medida que se implementan nuevos procesos de negocio.
  2. Es mas flexible que la opción 1.

Desventajas

  1. Es menos escalable porque se crean replicas de los objetos de dominio en el repositorio para hacer la comparación.
  2. La complejidad de implementación es media.
  3. Se requiere mayor cantidad de líneas de código para su implementación.
  4. Impacta minimamente el desempeño porque hay un poco mas de actividad en el garbage collector.
  5. Disminuye la mantenibilidad por tener un scope de datos a nivel de todo el proceso de negocio. Dado que todos los componente pueden alterar el estado de los datos en el repositorio tomaría mas tiempo hacer seguimiento cuando se tengan problemas.



Diseño 3. Basado en memento

Esta estrategia se basa en la filosofía del patrón de GOF Memento en el que se puede devolver un objeto a estados previos. Nuestra aproximación solo permite devolverse un estado hacia atrás. El diseño es el siguiente y no entro en mas detalles dada su simplicidad.

Ventajas

  1. Las mismas ventajas que en la opción 2.
  2. Baja complejidad de implementación
  3. Se requieren pocas líneas de código para su implementación.

Desventajas

  1. Modifica el modelo de dominio por lo que se hace un poco mas difícil de mantener pero tiene una ventaja respecto a la aproximación inicial dado que esta modificación se hace de forma estándard y no descontrolada como sería el adicionar a tributos de proceso según la necesidad.

Diseño 4. Patrón observador

Se trata de una mejora al diseño planteado en la opción 2 dado que se tendrá un repositorio de datos local al componente que lo requiere  (El Validador) y existirá solo un ente en capacidad de modificar sus datos (El Completador). Esto hará que el seguimiento a los errores esté mucho mas localizado con las ventajas propias del diseño 2.


Ventajas
  1. Las mismas que en la opción 2.
  2. Se requieren pocas líneas de código para su implementación.
  3. Aumenta la mantenibilidad por tener un repositorio con un scope mas reducido.

Desventajas
  1. El mismo issue de escalabilidad de la opción 2 porque se crean replicas de los objetos de dominio en el repositorio local.
  2. Impacta minimamente el desempeño porque hay un poco mas de actividad en el garbage collector.
  3. Alta complejidad de implementación
A continuación un diseño estructural de lo que plantea la solución.


El siguiente es un diagrama dinámico que describe la forma se articula en ejecución


En pocas palabras el diseño plantea lo siguiente: El orquestador se asegura de crear solo una instancia del validador que implementa Observer (Lo cual lo convierte en un objeto observador) y lo asigna a través de ObserverSetter quien tiene una característica especial. Todos los objetos que se almacenen en este vivirán durante la ejecución del hilo (Ver Thread Local).

Los objetos de negocio extenderán de la clase BaseBo la cual a su vez extiende de Observable (Esto hace que los Bo sean susceptibles a ser Observados por otro ente). En el constructor de BaseBo se asignará el Observador del objeto de negocio obteniendo la referencia de ObserverSetter. Es decir, cada vez que se instancie un Bo este se asignará automáticamente al validador como observador de su estado.

Todo lo anterior habilita la aplicación para que cada vez que se haga un cambio sobre el estado de un Bo se notifique al objeto Observer (El validador). Cuando se realice la notificación al validador este internamente guardará el estado anterior del objeto y en el momento que se realice la ejecución de las validaciones podrá obtener para cada objeto de negocio el estado previo.

Conclusiones

Finalmente la alternativa de diseño seleccionada fué la 4 dado que por sus características cumple con lo siguiente: Requiere pocas líneas de código lo cual hace que se tenga un menor impacto en el código construido, la complejidad de implementación es alta por los skills en patrones de diseño mas nó por el código en sí mismo y esto se compensa con la mentoria del arquitecto, es mantenible en el tiempo porque los componentes que afectan el repositorio de datos es limitado y localizado, aunque es menos escalable que otras aproximaciones se asume el impacto y se compensa a través del incremento de memoria RAM en el servidor y el impacto en desempeño es mínimo dado que todo se hace en memoria.

Me gustaría tener su retroalimentación al respecto. Aunque es un diseño que satisface ciertas características siempre existirán opciones alternativas que puede ser mucho mejores.