Olá, meu nome é Alexei Valyakin, estou escrevendo aplicativos para Android. Alguns meses atrás, falei em uma reunião da equipe Yandex.Taxi com desenvolvedores de dispositivos móveis. Meu relatório foi dedicado à transição para a arquitetura de RIBs em táxis (RIB significa os três principais roteadores, interatores e construtores). Aqui está o vídeo, e sob o corte - um compêndio:
- É hora de pular um pouco em um trem com um hype. Este é um tema clássico sobre arquitetura no Android.
Sério, hoje quero falar sobre como e por que implementamos essa arquitetura, quais dificuldades encontramos e como ela pode ajudá-lo.

Quando entrei na empresa, nossa equipe era composta por quatro pessoas. Já naquele momento, tivemos um grande número de dificuldades. O projeto era antigo, iniciado em 2012. Foram coletados alguns problemas técnicos, um dos quais é um IC construído incorretamente, muita variabilidade nas abordagens, testes que não cobrem tudo. E, em geral, havia um grande número de dificuldades e conflitos de mesclagem.
Em dois anos, crescemos para 12 pessoas, o que significa que aumentamos a paralelização do desenvolvimento de recursos. Conseqüentemente, existem ainda mais conflitos de mesclagem e, com muita coerência do código, você entende o que isso pode levar. Em algum momento, começamos a afundar e, de alguma forma, tivemos que descobrir. Parte desses problemas foi resolvida pela refatoração de pontos, parte da biblioteca de componentes, da qual vale a pena falar em um relatório separado.

O que todos os desenvolvedores querem? Arquitetura bonita, flexibilidade de desenvolvimento, facilidade de adicionar recursos e, é claro, reduzir a complexidade das mesclagens - porque basicamente causam alguns bugs que podem aparecer no estágio de lançamento, quando os recursos isolados são testados e funcionam bem. E quando eles se apossaram e atingiram o lançamento - hop, tudo desmoronou. Esta é uma imagem de exemplo que queríamos ver.

Como você pode ir com ela? É claro que existem muitas opções de como fazer algo bem. Vou falar sobre as principais abordagens e suas desvantagens. Claro, existem outras soluções.

MVP clássico. Que desafios enfrentamos no MVP clássico? Se olharmos para o exemplo do nosso projeto, descobrimos que há MVP Activity, MVP Fragment, MVP View. E resulta uma grande variabilidade no que precisa ser adicionado. Em alguns casos, você acha que precisa adicionar uma exibição e um fragmento. Acontece que adicionar um pequeno recurso com o qual o gerente chega até você é bastante difícil, porque geralmente está localizado em uma Atividade MVP separada.
O segundo problema que o MVP tem está relacionado ao fato de o roteador implorar. Você quer conectar as crianças de maneira flexível e ter algum tipo de essência para isso. Portanto, geralmente os MVPs chegam a algum tipo de roteador de fabricação própria ou qualquer outra coisa. E a abordagem orientada à visão é um sinal de menos. Em muitos padrões de MVP, é o apresentador que é injetado na visualização, isso já a torna menos passiva e viola a arquitetura limpa.

Viper é melhor. Ele tem uma entidade como roteador, ele é mais abstrato e, no entanto, possui vários desvantagens. Ele ainda possui lógica orientada a exibição, possui a camada de apresentador necessária pela qual a lógica de negócios passa, e isso nem sempre é verdade. A camada Exibir também é necessária, você não pode se livrar dela.
O principal problema é que essa arquitetura veio do mundo iOS, por isso precisa ser adaptada de uma certa maneira para o Android. Vi que existem algumas adaptações, e algumas nem mesmo nada, mas há desvantagens.

É claro que no mundo da arquitetura não existe uma bala de prata, cada arquitetura tem seus prós e contras. RIBs também têm contras. Em geral, o Uber introduziu essa arquitetura em grande parte no nível do conceito. Eles têm algumas classes abertas, não há exemplos complicados. Existem alguns tutoriais simples que você pode seguir. E ao alternar para qualquer arquitetura, segue-se uma grande quantidade de refatoração, o que você precisa fazer, mas não apenas os RIBs têm esse menos.

Em que consiste a arquitetura dos RIBs? Ela usa componentes Dagger. Sua classe principal, Builder, reúne todo esse componente, que consiste nas seguintes partes: Roteador, Interator. Presenter (View) - uma camada separada, às vezes pode estar presente, às vezes ausente. Ao mesmo tempo, o Presenter (View) pode ser mesclado em uma classe ou dividido se você tiver uma lógica de apresentação falsa.
O que mais é legal aqui? Como o Presenter (Exibir) é opcional, você adiciona novas telas da mesma maneira que os novos recursos de negócios. Sua estrutura é mais limpa e compreensível. A criança não sabe nada sobre os pais, e os pais sabem sobre os filhos. Vamos ver como isso se parece com um exemplo de uma estrutura simplificada.

Você sempre tem algum tipo de raiz. Este é o RIB raiz. Ele decide o que incluir em si, dependendo do estado do seu aplicativo: é um estado autorizado ou não autorizado. Vejamos um exemplo de nossa aplicação. Talvez você esteja no pedido ou não.
Como exemplo, outro recurso interessante do RIB. Você pode criar um RIB como uma tela modal e depois conectá-lo em princípio a partir de qualquer RIB. Como o RIB não sabe nada sobre os pais, ele pode fornecer as dependências necessárias para o RIB filho.

A estrutura dos módulos pode se parecer com isso. No momento, estávamos pensando em dividir nosso aplicativo em módulos. Ele estava sozinho conosco. De fato, tudo é implementado de maneira bastante clássica. Você tem algum tipo de módulo comum, que pode ser dividido em módulos ainda menores, dependendo do que você precisa. Você tem algum tipo de API principal, talvez rede, bancos de dados, etc. E em nosso sistema de coordenadas, um RIB específico é um módulo separado, inclui todos os Common, etc., o que precisa, incluindo reforços filho.
Se algumas coisas precisam ser combinadas entre vários RIBs, há exemplos com classes de recursos compartilhados que se destacam simplesmente em módulos separados.

Quais são as vantagens dos RIBs? Facilidade de teste, alto isolamento de código, abordagem de atividade única, sem problemas com fragmentos (quem trabalha entenderá) e uniformidade. Essa é uma arquitetura de plataforma cruzada, existe uma abordagem para iOS e Android. E se você tem duas equipes, essa é uma grande vantagem, porque elas falam a mesma língua.
Este é um ponto importante. Quer um pouco de vida útil sobre a implementação de RIBs? Suponha que você transfira dependências para si mesmo e comece a adicionar as funções de extensão dos herdeiros e entenda que tudo isso não é suficiente para você, você precisa adaptá-lo a si mesmo. No final, você apenas pega e transfere para suas aulas. E há outra maneira - quando você as transfere imediatamente para suas aulas, sem perder tempo com a primeira opção, e a adapta para você.
É hora de dar uma olhada no código e como tudo se parece.

Eles possuem um plug-in conveniente que permite gerar as classes necessárias para o RIB, sem perder tempo criando-as. Ele cria quatro classes principais - Builder, Interactor, Router e View, sobre as quais falarei mais detalhadamente nos slides a seguir. Também gera testes. Naturalmente, ele não os escreverá para você, e você terá que escrevê-los você mesmo, mas, no entanto, é bastante agradável. Agora, estamos pensando em criar um plug-in que simplifique a criação de novos módulos com RIBs. Este plugin conectaria imediatamente todas as dependências necessárias e levaria menos tempo para configurar o módulo.

Portanto, o Builder é um componente clássico do código de cola, cuja principal tarefa é montar tudo junto, montar um componente do Dagger e o View. Normalmente, o View chama apenas o construtor, nada complicado lá. Em alguns casos, pode ser inflado.

A segunda parte, que está no Builder, é sobre vícios, isto é, sobre como a criança obtém vícios de fora.

Ele possui uma interface de componente pai que define as dependências necessárias. Assim, no Construtor do componente filho, são fornecidas todas as dependências necessárias acima.

O Interactor é essencialmente a classe mais importante que é a lógica de negócios. Apenas injeções são permitidas nele. Esta é praticamente a coisa mais importante que está sendo testada. Ele recebe um evento da camada da interface do usuário usando eventos do Stream RX. Presenter é uma interface que define os métodos que meu evento fornece.
O que mais é conveniente para os RIBs? Pelo fato de que na camada Interator e Presenter você pode organizar a interação que você gosta. Pode ser MVP, MVVM e MVVI. Aqui todo mundo é livre para escolher o que gosta. Uma assinatura para eventos do Presenter pode ser algo assim.

E aqui está como o processamento desses eventos pode parecer.

Roteador - uma classe responsável por conectar crianças. Ele não tem lógica de negócios, ele próprio não faz com que as crianças se conectem. Isso torna o Interactor nesse conceito. Em essência, aqui vou dar um exemplo simplificado de como isso acontece. De fato, o Builder simplesmente chama o método Build, que coleta o RIB filho e conecta o filho diretamente usando o filho anexado, além de adicionar uma exibição. Na maioria das vezes, essa lógica pode ser encapsulada em uma transição separada, você pode configurar animações - tudo depende de suas necessidades.

A vista é o mais passiva possível nessa arquitetura. Ela não injeta nada em si mesma, não sabe quase nada. Nos casos mais simples, ele pode implementar a interface do Presenter, se você não tiver dificuldade em apresentá-la. Em casos mais complexos, essa lógica é dividida em duas classes. Ou seja, você tem uma classe separada do Presenter, que apenas mapeia os dados comerciais - por exemplo, na visualização do modelo.
Aqui está um exemplo de como o Interactor recebe eventos da interface do usuário. Dê uma olhada no fluxo Rx.

Você não pode simplesmente construir uma nova arquitetura. Quando você faz isso, especialmente em um projeto grande, algumas dificuldades começam. Você precisa entender que temos um projeto enorme: cerca de 20 atividades, se não mais, e cerca de 60 fragmentos. Toda essa lógica era muito fragmentada. Era necessário, de alguma forma, juntar tudo isso.

Antes de tudo, você precisa mesclar tudo em um único ponto de navegação, primeiro criar um objeto god - um certo roteador de atividades, onde você também gerenciará uma pilha de fragmentos, porque terá muito código antigo. Ninguém permitirá que você implemente uma nova arquitetura o dia inteiro e pare um negócio. Ao fazer isso, você precisará fazer amizade com uma pilha de RIBs. Os RIBs também têm naturalmente uma pilha - é acessível por baixo do capô. Mas o que é importante aqui? Muito código precisará ser preenchido por nós mesmos. O Uber não suporta rotação de tela, portanto, não se trata muito de restaurar o estado. Portanto, a primeira coisa que tive que fazer quando comecei a estudar essa arquitetura foi adicionar um herdeiro ao roteador, que suporta a restauração da hierarquia dos RIBs e de todo o estado do aplicativo.
Você precisará dar suporte à alternância de recursos. Nem um único projeto grande pode ficar sem ele. Agora, um de nossos desenvolvedores está desenvolvendo um conceito. Se alguém assistiu ao Mobius 2016,
falamos sobre o Plugin Factory, que permite conectar e desconectar dinamicamente certos blocos de lógica - não necessariamente partes de telas. Pode atuar, por exemplo, dependendo das experiências que vêm do servidor. Tudo é feito abstratamente e a interação é simplificada.
O fluxo de trabalho dos RIBs também é um conceito interessante que você pode precisar. É quando você tem vários RIBs que não sabem nada um do outro, estão aproximadamente no mesmo nível, mas ao mesmo tempo você precisa iniciar o processo com os dados na entrada e, no final, precisa reunir tudo.
E, por exemplo, telas modais. Temos um design super personalizado, portanto, quase não há caixas de diálogo clássicas. Tudo é auto-escrito, temos que implementar tudo nós mesmos.
O que você pode obter usando RIBs? Isolamento de código, arquitetura simples compreensível, maneira fácil de modularizar, livrar-se de fragmentos, abordagem de atividade única e conveniência do desenvolvimento paralelo de recursos.
Referências:
-
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/DeezerSampleO último link leva ao meu projeto de estimação. Seis meses atrás, comecei a desenvolver RIBs para experimentar essa arquitetura antes de apresentá-la a nós. E há casos mais ou menos reais que podem ajudá-lo. Eu experimentei muito, então há coisas controversas. Mas, em geral, você pode ver como funciona lá. E talvez a partir daí você leve algo para si mesmo.
Também pensamos em alocar tudo isso em uma biblioteca separada, como fizemos em nosso tempo com a biblioteca de componentes. Haverá essas costelas Yandex. Mas isso é no futuro. Eu te contei tudo, obrigado.