Quando seu aplicativo é construído em uma arquitetura de vários módulos, você precisa dedicar muito tempo para garantir que todas as comunicações entre os módulos sejam escritas corretamente no código. Metade deste trabalho pode ser confiada à estrutura do Dagger 2. O chefe do grupo Yandex.Map para Android, Vladimir Tagakov
Noxa, falou sobre os prós e contras da multi-
modularidade e organização conveniente dos módulos internos de DI usando os módulos Dagger 2.
- Meu nome é Vladimir, estou desenvolvendo o Yandex.Maps e hoje vou falar sobre a modularidade e o segundo Dagger.
Eu entendi a parte mais longa quando a estudei, a mais rápida. A segunda parte, sobre a qual fiquei sentado por várias semanas, vou lhe contar muito rapidamente e de forma concisa.

Por que iniciamos um processo difícil de dividir em módulos no Maps? Nós só queríamos aumentar a velocidade de construção, todo mundo sabe disso.
O segundo ponto do objetivo é reduzir o engate de código. Peguei o equipamento da Wikipedia. Isso significa que desejamos reduzir as interconexões entre os módulos para que os módulos sejam separados e possam ser usados fora do aplicativo. Declaração inicial do problema: outros projetos Yandex devem poder usar parte da funcionalidade do Maps exatamente como nós. E, para desenvolver essa funcionalidade, estamos envolvidos no desenvolvimento do projeto.
Eu quero jogar um chinelo em chamas em direção a [k] apt, que diminui a velocidade da montagem. Eu não o odeio muito, mas eu o amo muito. Ele me deixa usar Dagger.

A principal desvantagem do processo de separação de módulos é, paradoxalmente, a desaceleração da velocidade de montagem. Especialmente no início, quando você remove os dois primeiros módulos, Common e alguns de seus recursos, a velocidade geral de construção do projeto diminui, não importa como você tente. No final, quanto menos código permanecer no seu módulo principal, a velocidade de construção aumentará. E, ainda assim, isso não significa que tudo está muito ruim, há maneiras de contornar isso e até lucrar com o primeiro módulo.
A segunda desvantagem é que é difícil separar o código em módulos. Quem tentou, sabe que você está começando a extrair algum tipo de dependência, alguns clássicos, e tudo acaba copiando todo o seu módulo principal para outro módulo e recomeçando. Portanto, você precisa entender claramente o momento em que precisa parar e interromper a conexão usando algum tipo de abstração. A desvantagem é mais abstrações. Mais abstrações - design mais complexo - mais abstrações.
É difícil adicionar novos módulos Gradle. Porque Por exemplo, um desenvolvedor chega, leva um novo recurso ao desenvolvimento, imediatamente se sai bem, cria um módulo separado. Qual é o problema? Ele deve se lembrar de todo o código disponível, que está no módulo principal, para que, se houver, reutilize-o e coloque-o em comum. Como o processo de remoção de algum módulo no Common é constante até o módulo principal do aplicativo se transformar em uma camada fina.
Módulos, módulos, módulos ... Módulos Gradle, módulos Dagger, módulos de interface são horríveis.

O relatório consistirá em três partes: pequeno, grande e complexo. Primeiro, a diferença entre Implementação e API no AGP. O Android Gradle Plugin 3.0 apareceu relativamente recentemente. Como foi tudo antes dele?

Aqui está um projeto típico de um desenvolvedor íntegro, composto por três módulos: o módulo App, que é o principal, é montado e instalado no aplicativo e dois módulos de recursos.
Fale imediatamente sobre as flechas. É uma grande dor, todo mundo desenha na direção em que é conveniente para ele desenhar. Para mim, eles querem dizer que do Core há uma seta para o Feature. Portanto, o Feature conhece o Core, pode usar classes do Core. Como você pode ver, não há seta entre o Core e o aplicativo, o que significa que o aplicativo parece não estar usando o Core. O núcleo não é um módulo comum, é, todo mundo depende dele, é separado, há pouco código nele. Enquanto não vamos considerar isso.
Nosso módulo principal mudou, precisamos refazê-lo de alguma forma. Nós mudamos o código nele. Cor amarela - alteração de código.

Após a remontagem do projeto. É claro que, após a troca de um módulo, ele deverá ser reconstruído e recompilado. Ok

Após a montagem do módulo de recursos, isso depende dele. Também está claro, a dependência dele foi remontada e você precisa se atualizar. Quem sabe o que mudou lá.
E aqui acontece a coisa mais desagradável. O módulo App está funcionando, embora não esteja claro o porquê. Sei com certeza que não uso o Core de forma alguma e por que o aplicativo está sendo reconstruído não é claro. E ele é muito grande, porque no começo do caminho, e isso é uma dor muito grande.

Além disso, se vários recursos, muitos módulos dependem do Core, o mundo inteiro será remontado, leva muito tempo.
Vamos atualizar para a nova versão do AGP e substituir, como diz o manual, tudo em compilação com a API, e não com a implementação, como você pensava. Nada muda. Os esquemas são idênticos. Qual é a nova maneira de especificar dependências de implementação? Imagine o mesmo esquema usando apenas essa palavra-chave, sem uma API? Será assim.

Aqui na implementação, é claramente visto que há uma conexão entre o Core e o App. Aqui podemos entender claramente que não precisamos, queremos nos livrar dele, então remova-o. Tudo está se tornando mais fácil.

Agora quase tudo é bom, até mais do que isso. Se mudarmos alguma API no Core, adicionar uma nova classe, um novo método público ou privado de pacote, o Core e o Feature serão reconstruídos. Se você alterar a implementação dentro do método ou adicionar um método privado, teoricamente, a Recriação do Recurso não deve acontecer, porque nada mudou.

Vamos mais longe. Aconteceu que muitos dependem do nosso núcleo. O núcleo é provavelmente algum tipo de rede ou processamento de dados do usuário. Por ser a Rede, tudo muda com bastante frequência, tudo é reconstruído e temos a mesma dor da qual fugimos com cuidado.
Vejamos duas maneiras de lidar com isso.

Só podemos transferir as APIs do nosso módulo Core para um módulo separado, sua API, que usamos. E em um módulo separado, podemos executar a implementação dessas interfaces.

Você pode olhar para a conexão na tela. O Impl de núcleo não estará disponível para recursos. Ou seja, não haverá conexão entre os recursos e a implementação do Core. E o módulo, destacado em amarelo, fornecerá apenas fábricas que fornecerão algum tipo de implementação de suas interfaces desconhecidas para ninguém.
Após essa conversão, quero chamar a atenção para o fato de que a API principal, devido ao fato de a palavra-chave API estar permanente, estará disponível para todos os recursos de forma transitória.

Após essas transformações, mudamos algo na implementação que você faz com mais frequência e apenas o módulo com as fábricas será reconstruído, é muito leve, pequeno, você nem precisa considerar quanto tempo leva.

Outra opção nem sempre funciona. Por exemplo, se esse é algum tipo de rede, mal posso imaginar como isso pode acontecer, mas se esse é algum tipo de tela de login do usuário, pode ser que seja.

Podemos criar o Sample, o mesmo módulo raiz completo que o App, e coletar apenas um recurso, pois será muito rápido e poderá ser desenvolvido rapidamente iterativamente. No final da apresentação, mostrarei quanto tempo leva para criar e criar uma amostra.
Com a primeira parte finalizada. Quais módulos existem?

Existem três tipos de módulos. Comum, é claro, deve ser o mais leve possível e não deve conter nenhum recurso, mas apenas a funcionalidade usada por todos. Para nós, em nossa equipe, isso é especialmente importante. Se fornecermos nossos módulos de recursos a outros aplicativos, forçá-los-emos a arrastar o Common em qualquer caso. Se ele é muito gordo, ninguém vai nos amar.

Se você tem um projeto menor, com o Common você pode se sentir mais relaxado, e também não precisa ser muito zeloso.

O próximo tipo de módulo é Autônomo. O módulo mais comum e intuitivo que contém um recurso específico: algum tipo de tela, algum tipo de script do usuário e assim por diante. Deve ser o mais independente possível e, para isso, na maioria das vezes você pode criar um aplicativo de amostra e desenvolvê-lo nele. O aplicativo de amostra é muito importante no início do processo de divisão, porque tudo ainda está crescendo lentamente e você deseja obter lucro o mais rápido possível. No final, quando tudo é transformado em módulos, você pode reconstruir tudo, será rápido. Porque não será reconstruído mais uma vez.

Módulos de celebridades. Eu mesmo propus a palavra. O ponto é que ele é muito famoso por todos, e muitos dependem dele. A mesma rede. Eu já disse que, se você costuma montá-lo, como pode evitar o fato de que tudo lhe é remontado. Há uma outra maneira que pode ser usada para pequenos projetos para os quais não vale a pena dar tudo como um vício separado, um artefato separado.

Como é isso? Repetimos que retiram a API do Celebrity, retiram sua implementação e agora preste atenção, preste atenção nas setas de Feature para Celebrity. Isso está acontecendo. A API do seu módulo caiu em Comum, a implementação permaneceu nela e a fábrica que fornece a implementação dessa API apareceu no seu módulo principal. Se alguém assistiu Mobius, Denis Neklyudov falou sobre isso. Esquema muito semelhante.
Usamos o Dagger no projeto, gostamos e queríamos tirar o máximo proveito desse benefício no contexto de diferentes módulos.

Queríamos que cada módulo tivesse um gráfico de dependência independente, um componente raiz específico do qual fazer qualquer coisa, queríamos ter nosso próprio código gerado para cada módulo Gradle. Não queríamos que o código gerado se arrastasse para o código principal. Queríamos o máximo de validação em tempo de compilação possível. Sofremos de [k] apt, pelo menos devemos obter algum lucro com o que Dagger dá. E com tudo isso, não queríamos forçar ninguém a usar Dagger. Nem quem implementa o módulo de novos recursos separadamente, nem quem os consome, são nossos colegas que solicitam alguns recursos para si.
Como organizar um gráfico de dependência separado dentro do nosso módulo de recursos?

Você pode tentar usar o Subcomponente, e até funcionará. Mas isso tem algumas falhas. Você pode ver que no Subcomponente não está claro quais dependências ele usa do Componente. Para entender isso, você deve remontar o projeto com longa e dolorosa, ver o que Dagger jura e adicioná-lo.
Além disso, os subcomponentes são projetados de tal forma que forçam outras pessoas a usar o Dagger, e isso não funcionará facilmente para seus clientes e você mesmo se você decidir recusar em algum módulo.

Uma das coisas mais repugnantes é que, ao usar o Subcomponente, todas as dependências são inseridas no módulo principal. O Dagger é projetado para que os subcomponentes sejam gerados por uma classe incorporada de seus componentes de estrutura, o pai. Talvez alguém estivesse olhando para o código gerado e seu tamanho em seus componentes gerados? Temos 20 mil linhas nele. Como os subcomponentes sempre são classes aninhadas para componentes, acontece que os subcomponentes também são aninhados e todo o código gerado cai no módulo principal, esse arquivo de vinte linhas que precisa ser compilado e precisa ser refatorado, o Studio começa a desacelerar.
Mas existe uma solução. Você pode usar apenas o componente.

No Dagger, um componente pode especificar dependências. Isso é mostrado no código e mostrado na figura. Dependências nas quais você especifica métodos de Provisão, métodos de fábrica que mostram de quais entidades seu componente depende. Ele os quer no momento da criação.
Antes, eu sempre pensei que apenas outros componentes podem ser especificados nessas dependências, e é por isso - a documentação diz isso.

Agora eu entendo o que significa usar a interface do componente, mas antes eu pensava que era apenas um componente. De fato, você precisa usar uma interface composta de acordo com as regras para criar uma interface para um componente. Em resumo, basta fornecer métodos, quando você tiver getters para algum tipo de dependência. Você também pode encontrar um código de exemplo na documentação do Dagger.

O OtherComponent também está escrito lá, e isso é confuso, porque, de fato, você não pode apenas inserir componentes lá.
Como gostaríamos de usar esse negócio na realidade?

Na realidade, existe um módulo Feature, ele possui um pacote de API visível, localizado próximo à raiz de todos os pacotes e diz que existe um ponto de entrada - FeatureActivity. Não é necessário usar tipealias, apenas para deixar claro. Pode ser um fragmento, pode ser um ViewController - não importa. E existem suas dependências, FeatureDeps, onde é indicado que ele precisa de um contexto, algum tipo de serviço de rede, do Common, algum tipo de coisa que você deseja obter do aplicativo e qualquer cliente deve satisfazer isso. Quando ele faz, tudo vai funcionar.

Como usamos tudo isso no módulo de recursos? Aqui eu uso Activity, isso é opcional. Como de costume, criamos nosso próprio componente raiz do Dagger e usamos o método mágico findComponentDependencies, é muito semelhante ao Dagger para Android, mas não podemos usá-lo principalmente porque não queremos arrastar subcomponentes. Caso contrário, podemos tirar toda a lógica deles.
No começo, tentei dizer como funciona, mas você pode vê-lo no projeto de amostra na sexta-feira. Como isso deve ser usado pelos clientes da sua biblioteca no seu módulo principal?

Primeiro de tudo, são apenas tipealias. De fato, ele tem um nome diferente, mas, por uma questão de brevidade, é. A classe de interface MapOfDepth by Dependency fornece sua implementação. No aplicativo, dizemos que podemos criar dependências da mesma forma que no Dagger para Android, e é muito importante que o componente herde essa interface e receba automaticamente os métodos de provisionamento. A adaga a partir deste momento começa a nos forçar a fornecer essa dependência. Até que você o forneça, ele não será compilado. Esta é a principal conveniência: você decidiu criar um recurso, expandiu seu componente com essa interface - tudo até fazer o resto, ele não será apenas compilado, mas produzirá mensagens de erro claras. O módulo é simples, o ponto é que ele vincula seu componente à implementação da interface. Aproximadamente o mesmo que no Dagger para Android.
Vamos passar para os resultados.

Eu verifiquei nosso mainframe e meu laptop local antes de desligar tudo o que era possível. Se adicionarmos um método público ao Feature, o tempo de compilação será significativamente diferente. Aqui mostro as diferenças quando estou construindo um projeto de amostra. Isso é 16 segundos. Ou quando eu coleciono todos os cartões - isso significa dois minutos para ficar sentado e esperar por cada mudança, mesmo mínima. Portanto, muitos recursos que desenvolvemos e que serão desenvolvidos em projetos de amostra. No mainframe, o tempo é comparável.

Outro resultado importante. Antes de destacar o módulo Feature, era assim: no mainframe, eram 28 segundos, agora são 49 segundos. Alocamos o primeiro módulo e já recebemos uma desaceleração da montagem quase duas vezes.

E outra opção é uma montagem incremental simples do nosso módulo, não um recurso, como no anterior. Foram 28 segundos até o módulo ser alocado. Quando alocamos um código que não precisava ser reconstruído a cada vez, e o [k] apt, que não precisava ser executado a cada vez, vencemos três segundos. Deus sabe o que, mas espero que, a cada novo módulo, o tempo diminua.
Aqui estão links úteis para artigos:
API versus implementação ,
artigo com medições do tempo de construção ,
módulo de amostra . A apresentação estará
disponível . Obrigada