Multimodularidad y Daga 2. Conferencia Yandex

Cuando su aplicación se basa en una arquitectura de módulos múltiples, debe dedicar mucho tiempo a garantizar que todas las comunicaciones entre los módulos estén escritas correctamente en el código. La mitad de este trabajo se puede confiar al marco Dagger 2. El jefe del grupo Yandex.Map para Android, Vladimir Tagakov Noxa, habló sobre los pros y los contras de la multimodularidad y la organización conveniente de los módulos internos DI utilizando Dagger 2.


- Mi nombre es Vladimir, estoy desarrollando Yandex.Maps y hoy les contaré sobre la modularidad y la segunda Daga.

Entendí la parte más larga cuando la estudié yo misma, la más rápida. La segunda parte, sobre la que me senté durante varias semanas, te la contaré de manera rápida y concisa.



¿Por qué comenzamos un proceso difícil de dividir en módulos en Maps? Solo queríamos aumentar la velocidad de construcción, todos lo saben.

El segundo punto del objetivo es reducir el enganche de código. Tomé el equipo de Wikipedia. Esto significa que queríamos reducir las interconexiones entre los módulos para que los módulos estén separados y puedan usarse fuera de la aplicación. Declaración inicial del problema: otros proyectos de Yandex deberían poder usar parte de la funcionalidad de Maps exactamente como lo hacemos nosotros. Y para desarrollar esta funcionalidad, estamos comprometidos con el desarrollo del proyecto.

Quiero lanzar una zapatilla ardiente hacia [k] apt, lo que reduce la velocidad de montaje. No lo odio mucho, pero lo amo mucho. Me deja usar Dagger.



El principal inconveniente del proceso de separación del módulo es, paradójicamente, la desaceleración en la velocidad de ensamblaje. Especialmente al principio, cuando saca los dos primeros módulos, Común y algunas de sus funciones, la velocidad general de construcción del proyecto disminuye, sin importar cómo lo intente. Al final, como queda menos código en su módulo principal, aumentará la velocidad de compilación. Y aún así, esto no significa que todo esté muy mal, hay formas de evitar esto e incluso obtener ganancias del primer módulo.

El segundo inconveniente es que es difícil separar el código en módulos. Quien lo intentó, sabe que está comenzando a extraer algún tipo de dependencias, algunos clásicos, y todo termina copiando todo su módulo principal en otro módulo y comienza de nuevo. Por lo tanto, debe comprender claramente el momento en que debe detener y romper la conexión utilizando algún tipo de abstracción. La desventaja es más abstracciones. Más abstracciones - diseño más complejo - más abstracciones.

Es difícil agregar nuevos módulos Gradle. Por qué Por ejemplo, llega un desarrollador, lleva una nueva característica al desarrollo, inmediatamente lo hace bien, crea un módulo separado. Cual es el problema Debe recordar todo el código disponible, que está en el módulo principal, de modo que, en todo caso, lo reutilice y lo ponga en común. Porque el proceso de extraer algún módulo en común es constante hasta que el módulo principal de la aplicación se convierta en una capa delgada.

Módulos, módulos, módulos ... Los módulos Gradle, los módulos Dagger, los módulos de interfaz son horribles.



El informe constará de tres partes: pequeño, grande y complejo. Primero, la diferencia entre Implementación y API en AGP. Android Gradle Plugin 3.0 ha aparecido relativamente recientemente. ¿Cómo estuvo todo ante él?



Este es un proyecto típico de un desarrollador saludable, que consta de tres módulos: el módulo de la aplicación, que es el principal, se ensambla e instala en la aplicación, y dos módulos de funciones.

Habla inmediatamente sobre las flechas. Este es un gran dolor, todos dibujan en la dirección en la que es conveniente para él dibujar. Para mí, quieren decir que desde Core hay una flecha hacia Feature. Entonces, Feature sabe sobre Core, puede usar clases de Core. Como puede ver, no hay una flecha entre el Core y la aplicación, lo que significa que la aplicación no parece estar usando Core. Core no es un módulo común, lo es, todos dependen de él, está separado, tiene poco código. Si bien no lo consideraremos.

Nuestro módulo principal ha cambiado, tenemos que rehacerlo de alguna manera. Cambiamos el código en él. Color amarillo - cambio de código.



Después de reensamblar el proyecto. Está claro que después de cambiar un módulo, tendrá que ser reconstruido, recompilado. Esta bien



Después de que el módulo de características también se ensambla, lo que depende de ello. También está claro, su dependencia se ha reagrupado y necesita actualizarse. Quién sabe qué ha cambiado allí.

Y aquí sucede lo más desagradable. El módulo de la aplicación está funcionando, aunque no está claro por qué. Sé con certeza que no uso Core de ninguna manera, y no está claro por qué se está reconstruyendo la aplicación. Y él es muy grande, porque al principio del camino, y este es un dolor muy grande.



Además, si varias funciones, muchos módulos dependen de Core, entonces todo el mundo se volverá a montar, lleva mucho tiempo.

Actualicemos a la nueva versión de AGP y reemplacemos, como dice el manual, todos compilemos con la API, y no con la Implementación, como pensaba. Nada cambia Los esquemas son idénticos. ¿Cuál es la nueva forma de especificar dependencias de implementación? ¿Imagina el mismo esquema usando solo esta palabra clave, sin una API? Se verá así.



Aquí en la implementación, se ve claramente que hay una conexión entre Core y la aplicación. Aquí podemos entender claramente que no lo necesitamos, queremos deshacernos de él, así que simplemente quítelo. Todo se está volviendo más fácil.



Ahora casi todo está bien, incluso más que. Si cambiamos alguna API en Core, agregamos una nueva clase, un nuevo método público o privado de paquete, entonces Core y Feature se reconstruirán. Si cambia la implementación dentro del método o agrega un método privado, entonces la reconstrucción teórica de Feature no debería suceder en absoluto, porque nada ha cambiado.



Vamos más allá. Dio la casualidad de que muchos dependen de nuestro núcleo. Core es probablemente algún tipo de red o procesamiento de datos de usuario. Debido a que esto es Red, todo cambia con bastante frecuencia, todo se reconstruye y tenemos el mismo dolor del que nos escapamos con cuidado.
Veamos dos formas de lidiar con esto.



Solo podemos transferir las API de nuestro módulo Core a un módulo separado, su API, que usamos. Y en un módulo separado podemos sacar la implementación de estas interfaces.



Puedes mirar la conexión en la pantalla. Core Impl no estará disponible para funciones. Es decir, no habrá conexión entre las características y la implementación de Core. Y el módulo, resaltado en amarillo, solo proporcionará fábricas que proporcionarán algún tipo de implementación de sus interfaces desconocidas para cualquiera.

Después de tal conversión, quiero llamar la atención sobre el hecho de que la API Core, debido al hecho de que la palabra clave API está en pie, estará disponible para todas las funciones de forma transitiva.



Después de estas transformaciones, cambiamos algo en la implementación que haces con mayor frecuencia, y solo se reconstruirá el módulo con las fábricas, es muy ligero, pequeño, ni siquiera tienes que considerar cuánto tiempo lleva.



Otra opción no siempre funciona. Por ejemplo, si se trata de algún tipo de red, difícilmente puedo imaginar cómo puede suceder esto, pero si se trata de algún tipo de pantalla de inicio de sesión de usuario, entonces puede ser.



Podemos hacer Sample, el mismo módulo raíz completo que App, y recopilar solo una característica en él, será muy rápido y se puede desarrollar rápidamente de forma iterativa. Al final de la presentación, le mostraré cuánto tiempo lleva construir y crear una muestra.

Con la primera parte terminada. ¿Qué módulos hay?



Hay tres tipos de módulos. Lo común, por supuesto, debe ser lo más ligero posible, y no debe contener ninguna característica, sino solo la funcionalidad utilizada por todos. Para nosotros en nuestro equipo esto es especialmente importante. Si proporcionamos nuestros módulos de funciones a otras aplicaciones, los forzaremos a arrastrar Common en cualquier caso. Si es muy gordo, nadie nos amará.



Si tienes un proyecto más pequeño, entonces con Common puedes sentirte más relajado, entonces tampoco tienes que ser muy celoso.



El siguiente tipo de módulo es independiente. El módulo más común e intuitivo que contiene una característica específica: algún tipo de pantalla, algún tipo de script de usuario, etc. Debe ser lo más independiente posible, y para ello, con mayor frecuencia, puede hacer una aplicación de muestra y desarrollarla en ella. La aplicación de muestra es muy importante al comienzo del proceso de división, porque todo se está construyendo lentamente y desea obtener ganancias lo más rápido posible. Al final, cuando todo se divide en módulos, puede reconstruir todo, será rápido. Porque no será reconstruido una vez más.



Módulos de famosos. Yo mismo se me ocurrió la palabra. El punto es que es muy famoso por todos, y muchos dependen de él. La misma red. Ya dije, si a menudo lo vuelve a armar, ¿cómo puede evitar el hecho de que todo se vuelva a montar de usted? Hay otra manera que se puede utilizar para proyectos pequeños para los que no vale la pena el objetivo de dar todo como una adicción separada, un artefacto separado.



¿Cómo se ve? Repetimos que elimine la API de Celebrity, elimine su implementación y ahora mire sus manos, preste atención a las flechas de Feature a Celebrity. Esto esta pasando. La API de su módulo cayó en Común, la implementación permaneció en ella y la fábrica que proporciona la implementación de esta API apareció en su módulo principal. Si alguien miraba a Mobius, entonces Denis Neklyudov hablaba de ello. Esquema muy similar.

Usamos Dagger en el proyecto, nos gusta y queríamos aprovechar al máximo este beneficio en el contexto de diferentes módulos.



Queríamos que cada módulo tuviera un gráfico de dependencia independiente, que tuviera un componente raíz específico desde el que pudiera hacer cualquier cosa, queríamos tener nuestro propio código generado para cada módulo Gradle. No queríamos que el código generado se arrastrara al principal. Queríamos la mayor validación en tiempo de compilación posible. Sufrimos de [k] apt, al menos deberíamos obtener algún beneficio de lo que Dagger nos da. Y con todo esto, no queríamos obligar a nadie a usar Dagger. Ni el que implementa el módulo de funciones nuevas por separado, ni el que luego lo consume, son nuestros colegas que solicitan algunas funciones por sí mismos.

¿Cómo organizar un gráfico de dependencia separado dentro de nuestro módulo de características?



Puede intentar usar Subcomponente, e incluso funcionará. Pero esto tiene bastantes defectos. Puede ver que en Subcomponente no está claro qué dependencias usa de Componente. Para comprender esto, debe volver a armar el proyecto de manera larga y dolorosa, mirar lo que Dagger jura y agregarlo.
Además, los subcomponentes están dispuestos de tal manera que obligan a otros a usar Dagger, y no resultará fácil para sus clientes y usted mismo si decide rechazar en algún módulo.



Una de las cosas más desagradables es que cuando se usa Subcomponente, todas las dependencias se incorporan al módulo principal. Dagger está diseñado para que los subcomponentes sean generados por una clase integrada de sus componentes de trama, el padre. ¿Quizás alguien estaba mirando el código generado y su tamaño en sus componentes generados? Tenemos 20 mil líneas en él. Dado que los subcomponentes siempre son clases anidadas para componentes, resulta que los subcomponentes de los subcomponentes también están anidados, y todo el código generado cae en el módulo principal, este archivo de veinte líneas que debe compilarse y necesita ser refactorizado, Studio comienza a disminuir la velocidad - dolor.

Pero hay una solución. Puede usar solo Componente.



En Dagger, un componente puede especificar dependencias. Esto se muestra en el código y en la imagen. Dependencias en las que especifica métodos de aprovisionamiento, métodos de fábrica que muestran de qué entidades depende su componente. Los quiere en el momento de la creación.
Antes, siempre pensé que solo se pueden especificar otros componentes en estas dependencias, y esa es la razón: la documentación lo dice.



Ahora entiendo lo que significa usar la interfaz de componente, pero antes pensaba que era solo un componente. De hecho, debe usar una interfaz compuesta de acuerdo con las reglas para crear una interfaz para un componente. En resumen, simplemente aprovisione métodos, cuando solo tiene captadores para algún tipo de dependencias. También puede encontrar código de muestra en la documentación de Dagger.



OtherComponent también está escrito allí, y esto es confuso, porque de hecho, no solo puede insertar componentes allí.

¿Cómo nos gustaría utilizar este negocio en realidad?



En realidad, hay un módulo de características, tiene un paquete API visible, ubicado cerca de la raíz de todos los paquetes, y dice que hay un punto de entrada: FeatureActivity. No es necesario usar typealias, solo para aclararlo. Puede ser un fragmento, puede ser un ViewController, no importa. Y existen sus dependencias, FeatureDeps, donde se indica que necesita un contexto, algún servicio de red, algo común que desee obtener de la aplicación, y cualquier cliente está obligado a satisfacer esto. Cuando lo haga, todo funcionará.



¿Cómo usamos todo esto en el módulo de funciones? Aquí uso Activity, esto es opcional. Como de costumbre, creamos nuestro propio componente raíz Dagger y usamos el método mágico findComponentDependencies, es muy similar a Dagger para Android, pero no podemos usarlo principalmente porque no queremos arrastrar subcomponentes. De lo contrario, podemos tomar toda la lógica de ellos.

Al principio traté de decir cómo funciona, pero puedes verlo en el proyecto de muestra el viernes. ¿Cómo deberían usarlo los clientes de su biblioteca en su módulo principal?



En primer lugar, son solo typealias. De hecho, tiene un nombre diferente, pero por brevedad lo es. La clase de interfaz MapOfDepth by Dependency le ofrece su implementación. En la aplicación, decimos que podemos hacer dependencias de la misma manera que en Dagger para Android, y es muy importante que el componente herede esta interfaz y reciba automáticamente los métodos de provisión. Daga a partir de este momento comienza a obligarnos a proporcionar esta dependencia. Hasta que lo proporciones, no se compilará. Esta es la principal comodidad: decidió organizar una función, expandió su componente con esta interfaz; todo hasta que haga el resto, no solo se compilará, sino que producirá mensajes de error claros. El módulo es simple, el punto es que vincula su componente a la implementación de la interfaz. Aproximadamente lo mismo que en Dagger para Android.

Pasemos a los resultados.



Revisé nuestro mainframe y mi laptop local, antes de eso apagué todo lo que era posible. Si agregamos un método público a Feature, el tiempo de compilación es significativamente diferente. Aquí muestro las diferencias cuando estoy construyendo un proyecto de muestra. Esto es 16 segundos. O cuando recojo todas las cartas, significa dos minutos para sentarme y esperar cada cambio, incluso mínimo. Por lo tanto, desarrollamos muchas características que desarrollaremos en proyectos de muestra. En el mainframe, el tiempo es comparable.



Otro resultado importante. Antes de resaltar el módulo Feature, se veía así: en el mainframe eran 28 segundos, ahora son 49 segundos. Hemos asignado el primer módulo y ya recibimos una desaceleración del ensamblaje casi dos veces.



Y otra opción es un ensamblaje incremental simple de nuestro módulo, no una característica, como en el anterior. 28 segundos fueron hasta que se asignó el módulo. Cuando asignamos un código que no necesitaba ser reconstruido cada vez, y [k] apt, que no tenía que ejecutarse cada vez, ganamos tres segundos. Dios sabe qué, pero espero que con cada nuevo módulo, el tiempo solo disminuya.

Aquí hay enlaces útiles a artículos: API versus implementación , artículo con medidas de tiempo de construcción , módulo de muestra . La presentación estará disponible . Gracias

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


All Articles