Patrones de diseño usando Rx en soluciones móviles

Patrones de diseño usando Rx en soluciones móviles

En las aplicaciones móviles, el diseño de una funcionalidad que involucre la ejecución de múltiples tareas podría generar las siguientes preguntas:

  • ¿Tardará la ejecución de la tarea un tiempo considerable, 30 segundos o más?
  • ¿Se requiere notificar a la UI el resultado de la tarea?
  • ¿Hay dependencias con otras tareas?
  • ¿Los resultados de las tareas son insumo para otras tareas?
  • ¿La tarea debe ser ejecutada con la aplicación en foreground, background o en ambos?
  • ¿Qué ocurre con la ejecución de la tarea si la aplicación es cerrada?
  • ¿Qué debe ocurrir con la ejecución de la tarea si cambia la orientación del dispositivo?

Estas inquietudes deberían sin duda, ser resultas en un diseño antes de iniciar la implementación.

¿Entonces, por dónde empezar?

Lo primero es decidir a qué a actor se le debería delegar la ejecución de la tarea. ¿Se debería realizar en frontend, es decir en el dispositivo o se debería realizar en backend?

Si la tarea involucra un tiempo considerable de ejecución, es decir, tiempos de procesamiento superiores a 1 minuto o incluso a 30 segundos, quizás lo recomendable es delegarle la ejecución de la tarea a un componente de backend. Este componente bien podría ser una función serverless (serverless function) a través de Lambdas o Cloud Functions, responsables de realizar la transformación, validación o respectiva tarea prolongada para que posteriormente notifique al frontend el resultado.

También podría ser el caso si las tareas son programadas y requieren periodos extensos para su ejecución recurrente.

Por otro lado, podría existir tareas que su ejecución demandara ejecutarse del lado del dispositivo ya sea por la dependencia a un periférico o a la capacidad del dispositivo. Por ejemplo:

  • Recibir actualizaciones de ubicación a través del GPS.
  • Reproducir audio, capturar sonidos o capturar videos por un periodo de tiempo.
  • Consultar o depurar un mecanismo de persistencia local.
  • Un temporizador interno para control de sesión.

Para estos casos, en cuya orquestación y coreografía de las tareas queda delegada al frontend, podría recomendar los siguientes patrones.


Fan-out Pattern

Este patrón es muy usado en las soluciones que aplican el estilo de Arquitectura Serverless. Su objetivo es amplificar un evento en múltiples eventos para obtener  un resultado deseado.

En la siguiente figura se muestra un diagrama de ejemplo de dicho patrón:

Analizando el propósito de dicho patrón, se podría encontrar similitudes con la forma en la que se suele coordinar tareas a través de las extensiones Rx aplicando programación reactiva y funcional.

Aplicando multicasting a través de Rx, se obtiene las interacciones entre tareas como se muestra en la figura:

El componente Rx Chain recibe un evento y partir de dicho evento se activan la ejecución de múltiples tareas independientes. Dichas tareas podrían entregar resultados a la UI o simplemente aplicar alguna transformación o lógica de aplicación sin comunicarse con la UI.

El componente Rx Chain no es más que la definición de un Observable con su respectiva cadena de operadores que podría aplicar múltiples filtros y transformaciones.

¿Y cuál es la relación con el patrón fan-out?

Bien, con una variante en el diseño para introducir un Rx Chain conformado por un Subject en lugar de un Observable, se podría obtener amplificar el evento recibido y propagarlo en múltiples tareas así como se muestra en la siguiente diagrama:

Vale la pena advertir que un componente Subject debe usarse únicamente cuando es necesario. Es recomendable tener siempre como primera opción el uso de un Observable y solo en los casos en los que se requiere un componente que cumpla la función de proxy capaz de recibir y propagar eventos como en este caso, es válido. Subject es la versión mutable de un Observable, por lo tanto su uso debe ser estrictamente necesario para no caer en la aplicación de un anti-patrón que pueda degradar el diseño.

Adicionalmente, para que la aplicación del patrón sea consistente se recomienda que las tareas cumplan con las siguientes condiciones:

  • Las tareas no deben anidar código Rx,  esto para evitar escenarios de ciclos infinitos.
  • La tarea definida es independiente, no depende de otras tareas.
  • La tarea solo soporta ejecución en foreground, es decir, si la aplicación es cerrada o el hilo sobre el cual se ejecuta la tarea es terminado, no se mantiene el estado de la operación.

Este patrón de diseño es apropiado aplicarlo cuando las tareas relacionadas no requieren ser ejecutadas en un segundo plano que persista la ejecución de la operación. Es decir, que si la aplicación se cierra o pasa a segundo plano, no requiera mantener la ejecución de las mismas. 

¿Pero, qué hacer si se requiere ejecutar tareas en segundo plano y mantener su operación aun cuando se cierre la aplicación?

Para ese tipo de requerimiento es adecuado el siguiente patrón de diseño.


Task Emitter Pattern

Este patrón de diseño puede ser aplicado para orquestar y desacoplar la UI de las operaciones que requieren ser ejecutadas por un largo periodo de tiempo en el frontend. Independientemente si la aplicación se encuentra en foreground o background, la ejecución de la tarea mantiene su ejecución y al finalizar la operación se envía una notificación a la UI para su respectiva gestión.

El componente Rx Chain permite aplicar una previa orquestación y aplicar transformaciones antes de lanzar las tareas prolongadas.

Las operaciones realizadas por Rx Chain son diseñadas para ejecutarse en foreground, esto es importante tenerlo presente. Mientras que las tareas prolongadas podrían ser diseñadas para ejecutarse tanto en foreground como background.

Muy importante, tener en cuenta que Rx Chain no depende ni espera el resultado de las tareas en segundo plano. En este punto se debe mantener el desacoplamiento.

Task Executer es el componente responsable de la ejecución de la tarea prolongada, la cual puede o no ser ejecutada en segundo plano. Android e iOS cuentan con sus respectivas APIs disponibles para implementar el diseño. En el caso de Android, se puede emplear WorkManager, Foreground Service, Alert Manager. En el caso de iOS se puede emplear BackgroundTasks Framework.

Por otro lado se tiene el Notification Center, componente encargado de recibir los resultados de las operaciones en background y encargado de notificar al componente de vista el resultado. Notification Center podría corresponder al sistema de notificaciones propio de cada API en Android y iOS, o podría ser un servicio cloud tal como FCM o SNS.

Un diseño más elaborado podría incluir un componente responsable de gestionar la comunicación entre los ejecutores de las tareas en segundo plano. Podría ser incluso un Task Executer genérico dedicado únicamente a gestionar e implementado con las tecnologías ya mencionadas tanto del lado Android como del lado iOS (WorkManager, BackgroundTasks).

Es recomendable que las tareas cumplan con las siguientes condiciones para mantener la consistencia del diseño:

  • La tarea puede delegarle al Rx Chain la aplicación de cualquier regla de negocio previo a su ejecución.
  • Las tareas prolongadas están sujetas a las restricciones que cada una de las API imponga. Por ejemplo, que el dispositivo está en modo carga, que exista una conexión a internet, que el dispositivo se encuentre activo sin bloqueo y condiciones tan específicas como por ejemplo que el tiempo límite de ejecución de una tarea no debe superar los 30 segundos, entre otras.

Para concluir, este patrón de diseño es recomendado para desacoplar los componentes de la Vista de aquellas tareas de ejecución prolongada.


Referencias y links de interés


Previous
Next Post »
Thanks for your comment