Cómo implementamos la arquitectura RIB. Informe Yandex.Taxi

Hola, mi nombre es Alexei Valyakin, estoy escribiendo aplicaciones para Android. Hace unos meses, hablé en una reunión del equipo Yandex.Taxi con desarrolladores móviles. Mi informe se dedicó a la transición a la arquitectura de las RIB en los taxis (RIB significa los tres principales enrutadores, interactuadores, constructores). Aquí está el video, y debajo del corte - resumen:


- Es hora de saltar un poco en un tren con un bombo publicitario. Este es un tema clásico sobre arquitectura en Android.

En serio, hoy quiero contarte cómo y por qué implementamos esta arquitectura, qué dificultades encontramos y cómo puede ayudarte.



Cuando me uní a la empresa, nuestro equipo estaba formado por cuatro personas. Ya en ese momento tuvimos una gran cantidad de dificultades. El proyecto era antiguo, comenzó en 2012. Se recopilaron bastantes problemas técnicos, uno de los cuales es un CI mal construido, mucha variabilidad en los enfoques, pruebas que no cubren todo. Y en general, hubo una gran cantidad de dificultades y conflictos de fusión.

En dos años hemos crecido hasta 12 personas, lo que significa que hemos aumentado la paralelización del desarrollo de características. En consecuencia, hay aún más conflictos de fusión, y con mucha coherencia del código, entiendes a qué puede conducir esto. En algún momento, comenzamos a hundirnos y de alguna manera tuvimos que resolverlo. Parte de estos problemas se resolvió mediante la refactorización de puntos, parte por la biblioteca de componentes, de la que vale la pena hablar en un informe separado.



¿Qué quieren todos los desarrolladores? Hermosa arquitectura, flexibilidad de desarrollo, facilidad para agregar funciones y, por supuesto, reducir la complejidad de las fusiones, porque básicamente causan algunos errores que pueden aparecer en la etapa de lanzamiento, cuando las funciones aisladas se prueban y funcionan bien. Y cuando se apoderaron y alcanzaron el lanzamiento - salto, todo se vino abajo. Esta es una imagen de muestra a la que queríamos llegar.



¿Cómo puedes ir con ella? Está claro que hay muchas opciones sobre cómo hacer algo bien. Hablaré sobre los principales enfoques y sus desventajas. Por supuesto, hay otras soluciones.



MVP clásico. ¿Qué desafíos enfrentamos en el MVP clásico? Si miramos el ejemplo de nuestro proyecto, obtenemos que hay MVP Activity, MVP Fragment, MVP View. Y resulta una gran variabilidad en lo que debe agregarse. En algunos casos, cree que necesita agregar una vista y un fragmento. Entonces resulta que agregar alguna pequeña característica con la que el gerente recurre a usted es bastante difícil, porque generalmente se encuentra en una Actividad MVP separada.

El segundo problema que tiene MVP está relacionado con el hecho de que el enrutador pide. Desea conectar a los niños de manera flexible y para que tenga algún tipo de esencia para esto. Por lo tanto, generalmente los MVP vienen a algún tipo de enrutador hecho a sí mismo o algo más. Y el enfoque basado en la vista es un gran inconveniente. En muchos patrones MVP, es el presentador el que se inyecta en la vista, esto ya lo hace menos pasivo y viola la arquitectura limpia.



Viper es mejor. Tiene una entidad como un enrutador, está más abstraído y, sin embargo, tiene varios inconvenientes. Todavía tiene una lógica controlada por la vista, tiene la capa de presentador requerida a través de la cual pasa la lógica empresarial, y esto no siempre es cierto. También se requiere la capa Vista, no puede deshacerse de ella.

El principal problema es que esta arquitectura nos llegó del mundo de iOS, por lo que debe adaptarse de cierta manera para Android. Vi que hay algunas adaptaciones, y algunas de ellas incluso nada, pero hay desventajas.



Está claro que en el mundo de la arquitectura no hay una bala de plata, cada arquitectura tiene sus pros y sus contras. Las costillas también tienen contras. En general, Uber introdujo esta arquitectura en su mayor parte a nivel de concepto. Tienen bastantes clases abiertas, no hay ejemplos complicados. Hay algunos tutoriales simples que puede seguir. Y al cambiar a cualquier arquitectura, sigue una gran cantidad de refactorización, que debe hacer, pero no solo las RIB tienen este signo negativo.



¿En qué consiste la arquitectura RIB? Ella usa componentes Dagger. Su clase principal, Builder, reúne todo este componente, que consta de las siguientes partes: Router, Interactor. Presentador (Vista): una capa separada, a veces puede estar presente, a veces ausente. Al mismo tiempo, Presenter (View) puede fusionarse en una clase o dividirse si tiene una lógica de presentación falsa.

¿Qué más es genial aquí? Dado que Presentador (Ver) es opcional, agrega nuevas pantallas de la misma manera que las nuevas funciones empresariales. Su estructura es más limpia y comprensible. El niño no sabe nada sobre el padre, y el padre sabe sobre los niños. Veamos cómo se ve esto como un ejemplo de una estructura simplificada.



Siempre tienes algún tipo de raíz. Esta es la raíz RIB. Decide qué incluir en sí mismo, dependiendo del estado de su aplicación: es un estado autorizado o no autorizado. Veamos un ejemplo de nuestra aplicación. Tal vez estás en el pedido o no en el pedido.

Como ejemplo, otra característica genial de RIB. Puede crear un RIB como pantalla modal y luego conectarlo en principio desde cualquier RIB. Como la RIB no sabe nada sobre los padres, cualquier padre puede proporcionar las dependencias que son necesarias para la RIB secundaria.



La estructura de los módulos puede verse así. Por el momento, solo estábamos pensando en dividir nuestra aplicación en módulos. Estaba solo con nosotros. De hecho, todo se implementa de manera bastante clásica. Tiene algún tipo de módulo común, se puede dividir en módulos aún más pequeños dependiendo de lo que necesite. Tiene algún tipo de API central, tal vez red, bases de datos, etc. Y en nuestro sistema de coordenadas, un RIB específico es un módulo separado, incluye todo lo Común, etc., lo que necesita, incluyendo costillas de niño.

Si es necesario combinar algunas cosas entre varias RIB, hay ejemplos con clases de entidades compartidas que se destacan simplemente en módulos separados.



¿Cuáles son las ventajas de las RIB? Facilidad de prueba, alto aislamiento de código, enfoque de actividad única, sin dolor con fragmentos (el que trabaje lo entenderá) y uniformidad. Esta es una arquitectura multiplataforma, hay un enfoque tanto para iOS como para Android. Y si tienes dos equipos, esta es una gran ventaja, porque hablarán el mismo idioma.

Este es un punto importante. ¿Quieres un pequeño truco de vida sobre la implementación de RIB? Suponga que transfiere dependencias a usted mismo, luego comienza a agregar las funciones de extensión de los herederos y comprende que todo esto no es suficiente para usted, necesita adaptarlo usted mismo. Al final, simplemente los toma y los transfiere a sus clases. Y hay otra forma: cuando los transfieres inmediatamente a tus clases, sin perder tiempo en la primera opción, y lo adaptas por ti mismo.

Es hora de echar un vistazo al código y cómo se ve todo.



Tienen un complemento conveniente que le permite generar las clases que se necesitan para RIB, sin perder tiempo en crearlas. Crea cuatro clases principales: Builder, Interactor, Router y View, de las que hablaré con más detalle en las siguientes diapositivas. También genera pruebas. Naturalmente, él no los escribirá para usted, y tendrá que escribirlos usted mismo, pero, sin embargo, es bastante agradable. Ahora estamos pensando en crear un complemento que simplifique la creación de nuevos módulos con RIB. Este complemento conectaría inmediatamente todas las dependencias necesarias, y tomaría menos tiempo configurar el módulo.



Entonces, Builder es un componente clásico de código de pegamento, cuya tarea principal es ensamblar todo junto, ensamblar un componente Dagger y View. Por lo general, View solo llamará al constructor, no hay nada complicado allí. En algunos casos, puede estar inflado.



La segunda parte, que está en Builder, trata sobre las adicciones, es decir, sobre cómo el niño adquiere adicciones desde el exterior.



Tiene una interfaz de componente principal que define las dependencias que necesita. Por lo tanto, en el generador del componente secundario, se proporcionan todas las dependencias que necesita desde arriba.



Interactor es esencialmente la clase más importante que es la lógica de negocios. Solo se permiten inyecciones en él. Esto es prácticamente lo más importante que se está probando. Recibe un evento de la capa UI usando eventos Stream RX. Presenter es una interfaz que define los métodos que proporciona mi evento.

¿Qué más es conveniente para las RIB? Por el hecho de que en la capa de Interactor y Presentador puede organizar la interacción que desee. Puede ser MVP y MVVM y MVVI. Aquí todos son libres de elegir lo que les gusta. Una suscripción a los eventos de Presenter podría verse así.



Y así es como podría verse el procesamiento de estos eventos.



Router: una clase que se encarga de conectar a los niños. No tiene lógica empresarial; él mismo no hace que los niños se conecten. Esto hace a Interactor en tal concepto. En esencia, aquí doy un ejemplo simplificado de cómo sucede esto. De hecho, Builder simplemente llama al método Build, que recopila el RIB secundario y lo conecta directamente usando el secundario adjunto, además de agregar una vista. Muy a menudo, esta lógica puede encapsularse en una transición separada, puede configurar animaciones; todo depende de sus necesidades.



La vista es lo más pasiva posible en esta arquitectura. Ella no se inyecta nada, no sabe casi nada. En los casos más simples, puede implementar la interfaz del presentador si no tiene dificultades para presentarla. En casos más complejos, esta lógica se divide en dos clases. Es decir, tiene un presentador de clase separado, que solo asigna datos comerciales, por ejemplo, en la vista de modelo.

Aquí hay un ejemplo de cómo Interactor recibe eventos de UI. Echa un vistazo a la transmisión Rx.



No puedes simplemente construir una nueva arquitectura. Cuando haces esto, especialmente en un proyecto grande, comienzan ciertas dificultades. Debe comprender que tenemos un gran proyecto: alrededor de 20 actividades, si no más, y alrededor de 60 fragmentos. Toda esta lógica estaba muy fragmentada. Era necesario fusionar todo esto de alguna manera.



En primer lugar, debe fusionar todo en un único punto de navegación, primero hacer un objeto divino: un determinado Enrutador de actividad, donde también administrará una pila de fragmentos, porque tendrá una gran cantidad de código antiguo. Nadie le permitirá implementar una nueva arquitectura todo el día y detener un negocio. Al hacerlo, deberá hacer amigos con una pila de costillas. Las RIB también tienen naturalmente una pila: se puede acceder desde debajo del capó. ¿Pero qué es lo importante aquí? Mucho código tendrá que ser completado por nosotros mismos. Uber no admite la rotación de la pantalla, por lo que no se trata tanto de restaurar el estado. Por lo tanto, lo primero que tuve que hacer cuando comencé a estudiar esta arquitectura fue agregar un heredero sobre el enrutador, que permite restaurar la jerarquía de las RIB y todo el estado de la aplicación.

Deberá admitir la alternancia de funciones. Ningún proyecto grande puede prescindir de él. Ahora uno de nuestros desarrolladores está desarrollando un concepto. Si alguien vio Mobius 2016, hablamos sobre Plugin Factory en él, lo que le permite conectar y desconectar dinámicamente ciertos bloques de lógica, no necesariamente piezas con pantallas. Puede actuar, por ejemplo, dependiendo de los experimentos que provienen del servidor. Todo se hace de manera abstracta y la interacción se simplifica.

El flujo de trabajo de RIB también es un concepto interesante que puede necesitar. Esto es cuando tiene varias RIB que no se conocen entre sí, están aproximadamente al mismo nivel, pero al mismo tiempo necesita comenzar el proceso con datos en la entrada, y al final tiene que poner todo junto.

Y, por ejemplo, pantallas modales. Tenemos un diseño súper personalizado, por lo que casi no quedan cuadros de diálogo clásicos. Todo está escrito por uno mismo, tenemos que implementar todo nosotros mismos.

¿Qué se puede obtener con RIB? Aislamiento de código, arquitectura simple y comprensible, forma fácil de modularización, eliminación de fragmentos, enfoque de actividad única y conveniencia del desarrollo paralelo de características.

Referencias
- github.com/uber/RIBs
- github.com/uber/RIBs/tree/master/android/tutorials
- habr.com/en/company/livetyping/blog/320452
- youtu.be/Q5cTT0M0YXg
- github.com/xzaleksey/Role-Playing-System-V2
- github.com/xzaleksey/DeezerSample

El último enlace lleva a mi proyecto favorito. Hace seis meses, comencé a desarrollar en RIB para probar esta arquitectura antes de presentarla. Y hay casos más o menos reales que pueden ayudarlo. Experimenté mucho, así que hay cosas controvertidas. Pero en general, puedes ver cómo funciona allí. Y tal vez a partir de ahí tomarás algo para ti.

También pensamos en asignar todo esto en una biblioteca separada, como lo hicimos en nuestro tiempo con la biblioteca de componentes. Habrá tales costillas Yandex. Pero esto es en el futuro. Te lo dije todo, gracias.

Source: https://habr.com/ru/post/467523/


All Articles