Em 20 de dezembro de 2016, os caras da Uber Engineering publicaram
um artigo sobre a nova arquitetura (aqui está a
tradução deste artigo no hub). Apresento a você a tradução da parte principal da documentação.
Para que serve a arquitetura dos RIBs?
RIBs é uma estrutura de arquitetura de plataforma cruzada do Uber. Ele foi projetado para grandes aplicativos móveis com um grande número de estados incorporados.
Ao desenvolver essa estrutura, os engenheiros da Uber aderiram aos seguintes princípios:
- Suporte à colaboração entre pessoas que desenvolvem em diferentes plataformas: a grande maioria das partes complexas dos aplicativos Uber é semelhante no iOS e no Android. Os RIBs fornecem padrões de desenvolvimento comuns para Android e iOS. Ao usar RIBs, os engenheiros no iOS e no Android podem compartilhar uma arquitetura desenvolvida em conjunto para suas funções.
- Minimização de estados e decisões globais : as mudanças globais de estado podem levar a um comportamento imprevisível e podem tornar impossível saber a que essas ou essas alterações no código do programa levarão. A arquitetura baseada em RIBs incentiva estados encapsulados em uma hierarquia profunda de RIBs bem isolados para evitar problemas com estados globais.
- Testabilidade e isolamento: as classes devem ser simples para poder escrever testes de unidade e também ter um motivo para serem isoladas (consulte o SRP ). As classes RIB individuais têm responsabilidades diferentes (por exemplo, roteamento, lógica comercial, lógica de apresentação, criação de outras classes RIB). Além disso, a lógica do RIB pai é basicamente separada da lógica do RIB filho. Isso facilita o teste de classes RIB e reduz a dependência entre os componentes do sistema.
- Ferramentas para desenvolvimento produtivo: pedir emprestado padrões arquitetônicos não triviais pode levar a problemas com o crescimento do aplicativo se não houver ferramentas confiáveis para suportar a arquitetura. A arquitetura do RIBs vem com ferramentas IDE para criar código, análise estática e integração de tempo de execução, o que melhora a produtividade do desenvolvedor em equipes grandes e pequenas.
- O princípio da abertura-proximidade: os desenvolvedores, se possível, devem adicionar novas funções sem alterar o código existente. Ao usar RIBs, a implementação desta regra pode ser vista em vários locais. Por exemplo, você pode anexar ou criar um RIB filho complexo que exija dependências no seu RIB pai, com pouca ou nenhuma alteração no RIB pai.
- Estruturação em torno da lógica de negócios: A estrutura da lógica de negócios de um aplicativo não deve refletir estritamente a estrutura da interface do usuário. Por exemplo, para facilitar a animação e o desempenho de uma exibição, a hierarquia da exibição pode ser menor que a hierarquia do RIB. Ou, uma única função RIB pode controlar a aparência de três visualizações que aparecem em locais diferentes na interface do usuário.
- Contratos exatos: os requisitos devem ser declarados usando contratos que são verificados em tempo de compilação. Uma classe não deve ser compilada se suas próprias dependências, bem como as dependências de convidados, não forem satisfeitas. A arquitetura dos RIBs usa o ReactiveX para representar dependências de convidados, sistemas de injeção de dependência de tipo seguro ( DI ) para representar dependências de classe e muitos outros recursos de DI para ajudar a criar invariantes de dados.
RIBs de elementos componentes
Se você já trabalhou com a arquitetura
VIPER , as classes que compõem o RIB parecerão familiares para você. Os RIBs geralmente consistem nos seguintes elementos, cada um dos quais é implementado em sua própria classe:

Interactractor
O Interactor contém lógica de negócios. Nesta classe, as notificações Rx são assinadas, são tomadas decisões sobre a alteração do estado, armazenamento de dados e anexação de RIBs filho.
Todas as operações realizadas no Interactor devem ser limitadas ao seu ciclo de vida. O Uber criou um kit de ferramentas para garantir que a lógica de negócios seja executada apenas com interação ativa. Isso evita que os Interatores sejam desativados, mas as assinaturas Rx ainda são acionadas e causam atualizações indesejadas na lógica de negócios ou no estado da interface do usuário.
Roteador
O roteador monitora eventos do Interactor e converte esses eventos em anexar e desanexar RIBs filhos. O roteador existe por três razões simples:
- O roteador existe como um objeto passivo, o que simplifica o teste da lógica complexa do Interactor, sem a necessidade de criar stubs para os Interatores filhos ou de alguma outra maneira para cuidar de sua existência.
- Os roteadores criam uma camada extra de abstração entre os Interatores pai e filho. Isso torna a comunicação síncrona entre os Interatores um pouco mais complexa e incentiva o uso de comunicações Rx em vez de comunicações diretas entre RIBs.
- Os roteadores contêm lógica de roteamento simples e repetitiva que, de outra forma, seria implementada nos Interatores. A portabilidade desse código padrão para os Roteadores ajuda os Interatores a serem pequenos e mais focados na lógica comercial principal do RIB.
Construtor
O Builder é necessário para criar instâncias para todas as classes incluídas no RIB, bem como criar instâncias do Builders para RIBs filhos.
O destaque da lógica de criação de classe no Builder adiciona suporte à capacidade de criar stubs no iOS e torna o restante do código RIB insensível aos detalhes da implementação de DI. O Builder é a única parte do RIB que precisa estar ciente do sistema de DI usado no projeto. Ao implementar outro Construtor, você pode reutilizar o restante do código RIB no projeto usando um mecanismo de DI diferente.
Apresentador
Presenter é uma classe sem estado que traduz um modelo de negócios em um modelo de apresentação e vice-versa. Ele pode ser usado para facilitar as transformações de visualização do modelo de teste. No entanto, muitas vezes essa tradução é tão trivial que não justifica a criação de uma classe separada do Presenter. Se o Presenter não for concluído, a tradução dos modelos de visualização se tornará responsabilidade do View (Controller) ou do Interactor.
Vista (Controlador)
O View cria e atualiza a interface do usuário. Inclui a criação e organização de componentes da interface, manipulação da interação do usuário, preenchimento de componentes da interface do usuário com dados e animação. O View foi projetado para ser o mais "burro" (passivo) possível. Eles simplesmente exibem informações. Em geral, eles não contêm nenhum código para o qual os testes de unidade devem ser gravados.
Componente
Componente é usado para gerenciar dependências de RIB. Ajuda o construtor a instanciar as outras classes que compõem o RIB. O componente fornece acesso a dependências externas necessárias para criar o RIB, bem como a suas próprias dependências criadas pelo próprio RIB, e controla o acesso a eles a partir de outros RIBs. O componente do RIB pai normalmente é incorporado no RIB-Builder filho para fornecer ao RIB filho acesso às dependências do RIB pai.
Gestão estatal
O estado do aplicativo é gerenciado e representado principalmente por RIBs atualmente conectados à árvore do RIB. Por exemplo, quando um usuário passa por diferentes estados em um aplicativo de viagem compartilhada simplificado, o aplicativo anexa e desanexa os seguintes RIBs:

Os RIBs apenas tomam decisões estatais dentro de sua competência. Por exemplo, o LoggedIn RIB apenas toma a decisão de fazer a transição entre estados como Request e OnTrip. Ele não toma nenhuma decisão sobre como deve ser o comportamento do sistema quando estamos na tela do OnTrip.
Nem todos os estados podem ser salvos adicionando ou removendo RIBs. Por exemplo, quando as configurações do perfil de usuário são alteradas, o RIB não liga ou desconecta. Como regra, salvamos esse estado dentro dos fluxos de modelos imutáveis, que reenviam valores ao trocar de peça. Por exemplo, o nome de usuário pode ser armazenado no arquivo ProfileDataStream, que é da competência do LoggedIn. Somente respostas de rede têm acesso de gravação a este fluxo. Estamos passando uma interface que fornece acesso de leitura a esses threads no gráfico DI.
Não há nada nos RIBs que seja a verdade última para o estado dos RIBs. Isso contrasta com o fato de que estruturas mais avançadas, como o React, já são fornecidas prontas para uso. No contexto de cada RIB, você pode escolher padrões que facilitam o fluxo de dados unidirecional ou pode deixar o estado da lógica de negócios e o estado de exibição temporariamente se desviar da norma, a fim de aproveitar as estruturas de animação eficientes para a plataforma.
Interação entre RIBs
Quando o Interactor toma uma decisão da lógica de negócios, pode ser necessário informar o outro RIB sobre eventos, como a conclusão e o envio de dados. A estrutura do RIB não inclui nenhuma maneira única de transferir dados entre os RIBs. No entanto, esse método foi projetado para facilitar alguns padrões comuns.
Como regra, se a conexão diminuir para o RIB filho, transmitiremos essas informações como eventos no fluxo Rx. Ou os dados podem ser incluídos como um parâmetro no método
build () do RIB filho; nesse caso, esse parâmetro se torna invariável para a vida útil do filho.

Se a conexão sobe na árvore do RIB para o Interator RIB pai, essa conexão é feita através da interface do ouvinte, pois o RIB pai pode ter um ciclo de vida mais longo que o RIB filho. Um RIB pai, ou algum objeto em seu gráfico DI, implementa uma interface de ouvinte e o coloca em seu gráfico DI, para que seus RIBs filhos possam chamá-lo. O uso desse modelo para transferir dados a montante, em vez de os RIBs pai se inscreverem diretamente nos fluxos Rx dos RIBs filhos deles tem várias vantagens. Ele evita vazamentos de memória, permite escrever, testar e manter RIBs pai sem saber quais RIBs filhos estão anexados a eles e também reduz a quantidade de barulho necessário para anexar / desanexar um RIB filho. Os fluxos ou ouvintes Rx não precisam cancelar o registro ou se registrar novamente com esse método de anexar um RIB filho.

RIB Toolkit
Para garantir uma implementação suave da arquitetura do RIB em aplicativos, os engenheiros da Uber criaram ferramentas para simplificar o uso do RIB e usar invariantes criados pela implementação da arquitetura do RIB. O código fonte deste kit de ferramentas foi parcialmente aberto e é mencionado nos
exemplos (veja a parte correta - aprox. Por.).
O kit de ferramentas, atualmente de código aberto, inclui:
- Gerador de código: plugins IDE para criar novos RIBs e testes relacionados.
- NPE Static Analyzer (Android): o NullAway é uma ferramenta de análise estática que permite esquecer as NullPointerExceptions.
- Analisador de posicionamento automático estático (Android): impede os vazamentos de memória mais comuns no RIB.
Kit de ferramentas para o qual o Uber planeja abrir código-fonte no futuro:
- Analisador estático para evitar vários vazamentos de memória no RIB
- Integração RIB com um detector de vazamento de memória durante a execução do programa
- (Android) Processadores de anotação para testes mais fáceis
- (Android) Analisador estático RxJava que fornece aos RIBs visualizações inalteradas do encadeamento principal
PS
Nós do
sports.ru gostamos muito da abordagem dos engenheiros da Uber, porque muitas vezes encontramos todos os problemas de arquitetura descritos no artigo. Apesar da razoabilidade, o RIB tem várias desvantagens, por exemplo, um limite bastante alto para entrar na arquitetura. Analisaremos mais detalhadamente os prós e contras da arquitetura nos seguintes artigos, eles estão planejados pelo menos dois - para iOS e Android. Para aqueles que querem mergulhar no RIB agora, há uma coluna na página wiki à direita que tem aulas de inglês. Por conta própria, observo que a arquitetura nasceu claramente em longas discussões técnicas e reuniu as melhores práticas para a construção de arquiteturas para aplicativos móveis atualmente disponíveis. E, finalmente, um pouco de relações públicas - nós do
sports.ru também adoramos discussões técnicas, geralmente realizamos oficinas técnicas para colegas, aprendemos regularmente novas tecnologias e, em geral, temos uma ótima atmosfera. Então, se você deseja fazer parte de nossa equipe, seja
bem -
vindo !