Arquitectura modular para una app mobile de gran escala

Facundo Rodríguez
Flux IT Thoughts
Published in
5 min readJun 25, 2020

--

Cuando comenzamos el proyecto en uno de nuestros clientes más importantes del sector bancario, nos encontramos con una forma de trabajar orientada a squads o células, en la que cada squad se conforma por un equipo de 5 o 6 personas con diferentes perfiles, lo que permite su funcionamiento autónomo en la organización.

En este artículo voy a contarles el camino que recorrimos en el desarrollo de una aplicación mobile nativa iOS & Android de gran escala, y las decisiones que tomamos para llegar a buen puerto implementado CI/CD nativo sobre el modelo de squads.

Squads

Cada squad se encargó de desarrollar ciertas funcionalidades de la app mobile. Pero las features necesitan ser ensambladas para formar aplicaciones deployables en los Stores (Play Store, App Store). Esa forma de trabajo requirió modularizar el desarrollo nativo.

Módulos

Para simplificar la división de trabajo, se crearon diferentes módulos funcionales que estaban a cargo de algún squad. Ese equipo se encargaba del desarrollo, testing y construcción del módulo.

Con el objetivo de abstraerse de la aplicación, cada módulo contó con un repositorio Git propio, además de que definió puntos de entrada a funcionalidades que pueden ser encendidas o apagadas en ejecución (llave de corte o feature flag).

Para Android se utilizó Kotlin como lenguaje de programación, y en iOS usamos Swift. En tanto, para el manejo de las dependencias usamos Gradle y Cocoapod respectivamente.

Generamos también nuestros propios arquetipos de módulos y aplicaciones para simplificar la construcción e incluir lineamientos en los ejemplos (en Android, arquetipos Gradel, y en iOS, templates de Cocoapod).

Aplicación

En ambas plataformas, la aplicación tiene la mínima lógica para iniciar sus módulos y resolver la configuración multiambiente (dev, test y producción). Los módulos son incorporados a la aplicación como dependencias (Gradle & Cocoapod) con una versión particular.

Permitimos el uso de dependencias locales: cada desarrollador clona los repositorios Git de la aplicación y los módulos con los que trabaja al momento de armar el workspace. En ese caso, se manejan las dependencias de los módulos en desarrollo (con una referencia al path donde se encuentra el código del módulo) y no requiere una versión cerrada del mismo (para ambas plataformas Android/iOS).

El resto de los módulos funcionales de otro squad se maneja como librería con una versión cerrada. En este caso el desarrollador, a diferencia de lo anterior, no va a trabajar con esos módulos y necesita una versión estable de los módulos de otros equipos.

Decisiones

En cuanto a las decisiones tomadas para llevar adelante la modularización, podemos destacar:

  • La necesidad de ser homogéneos en iOS y Android, de forma que ambas versiones de la aplicación tengan la mayor similitud posible en la implementación, simplificando el entendimiento para cualquier dev.
  • Desarrollar un módulo core nativo, que resuelva necesidades comunes a todos los módulos, y que incluya: networking, eventos internos y analíticos, seguridad, inyección de dependencias, manejo de llaves de corte, etc.
  • CI/CD mobile: evaluamos varias herramientas SaaS para realizar CI/CD. Entre todas, optamos por Bitrise para la construcción automatizada de módulos y aplicaciones nativas. Su simplicidad fue el diferencial qué más ponderamos. En los workflows de Bitrise, se contempló la construcción, el testeo unitario y el despliegue para todos los componentes. Para el caso de aplicaciones mobile, generamos un workflow para el despliegue a Google Play Alpha (Android) y TestFlight (iOS).
  • Utilización de llaves de corte (feature flag): las llaves de corte permiten “encender” o “apagar” una funcionalidad de una aplicación en ejecución (sin necesidad de redeployar). Elegimos Split.io para la administración de llaves de corte de la aplicación. Cada módulo expone sus funcionalidades principales, que tienen asociadas llaves de corte. Además se definieron llaves a nivel aplicación, que permiten controlar situaciones de fuerza mayor, como por ejemplo, actualizaciones obligatorias.
  • Evitar interdependencias entre módulos, para facilitar la construcción de la app. Sólo se crearon 3 módulos comunes (UI, commons y core), y no se permitieron dependencias inter-módulos funcionales.

También se definieron eventos internos, componente común de navegación e inyección de dependencias con interfaces públicas, para evitar dependencias físicas entre módulos.

CI/CD con Bitrise

Cuando comenzamos a usar Bitrise en todos los módulos y en la aplicación, nos encontramos con una herramienta simple y que aporta diversos beneficios: detecta el tipo de aplicación y genera workflows de construcción estándar funcional. Además, incorpora múltiples steps pre configurables para la construcción de workflows customizados. Si no existe un step que resuelva la tarea específica que se busca, siempre se puede recurrir a un script bash para resolverla.

Bitrise suma también un store de aplicaciones propio, donde se guardan los artefactos construidos por el workflow (ipa, apk, etc.), y se pueden compartir del store interno con el equipo de QA y devs.

Para fomentar el reuso de los workflows y no repetir la configuración por cada módulo de la app, implementamos workflows genéricos. La idea es generar un repositorio Git con diferentes archivos yml que definen workflows completos o fragmentos de workflows. Luego, desde el proyecto donde se los requiere utilizar (módulo o aplicación concreta), se los invoca con una serie de parámetros. Esto garantiza reutilización y simplifica el mantenimiento general en la herramienta.

Finalmente, por cada push en los repositorios se configuraron diferentes triggers en Bitrise (usando webhooks de Gitlab), que disparan la ejecución de workflows que, según el branch o si es un tag, testean, construyen y publican módulos y aplicaciones de forma automática.

Conclusión

El desarrollo modular nativo significó en el proyecto una gran decisión inicial y generó muchos desafíos que pudimos ir sorteando a partir de las decisiones tomadas. También incluyó grandes beneficios, permitiendo, entre otras cosas:

  • Trabajar con un equipo de gran envergadura en una sola app, sin que resulte caótico.
  • A nivel equipo, generar una expertise funcional, haciendo foco en un ámbito acotado de toda la lógica de negocio.
  • Ocultar los detalles de implementación internos del módulo y ganar flexibilidad a la hora de desarrollar.
  • Simplificar la incorporación de nuevas funcionalidades.

Un aspecto clave del éxito en la modularización de apps mobile es sin dudas contar con una plataforma de integración continua y despliegue continuo (CI/CD) que agilice y simplifique la construcción de los módulos y aplicaciones. Y encontramos en Bitrise un gran aliado para realizar esta tarea.

Learn more about Flux IT: Website · Instagram · LinkedIn · Twitter · Dribbble · Breezy

--

--