Arquitetura limpa nas apps: utilizando VIPER no Android

Diogo Cabral
Android Dev BR
Published in
10 min readJun 13, 2019
tipo de cobra conhecida como Viper

Com o crescimento do desenvolvimento de software e seus vários métodos e formas de escalar, é notória a necessidade de uma organização mínima no código, para que vários fatores funcionem bem a medida que a complexidade vai aumentando ou que mais pessoas participem da construção do código.

Neste artigo, será apresentado uma forma de fazer a construção de apps para a plataforma Android que se adeque às necessidades citadas acima, além de compará-la com outras já existentes e amplamente adotadas durante a história.

A importância de uma arquitetura adequada

Quando eu fui montar uma palestra para falar sobre arquitetura de código nas aplicações móveis, pensei em uma maneira de como ilustrar a importância de utilizá-las no nosso dia a dia, como uma coisa necessária e não apenas uma firula para aplicarmos no tempo livre.

Não precisei ir longe para conseguir o exemplo ideal. Se pararmos para pensar em, por exemplo, dez anos atrás, tudo era muito, mas muito diferente. Quem nunca escreveu uma aplicação Android onde a Activity era a sua classe principal (com regras de negócio, tratamento de dados, etc)?

Abaixo um exemplo retratando exatamente essa situação do passado (ou que idealmente deveria ter se mantido lá):

exemplo de código de activity sem responsabilidade única

Agora que você viu este exemplo, pode responder as seguintes questões:

  • Quanto tempo você demorou para entender o código (considerando que você tentou entender ele)?
  • Como você testaria este código de maneira simples e clara?
  • Você teria coragem de mexer neste código para adicionar uma nova funcionalidade ao projeto?

Difícil, não? Pois é. A forma como você responde essas três perguntas pode indicar claramente que seu código está precisando de uma reorganização para que se adeque a alguma arquitetura de código.

As arquiteturas famosas e suas limitações

Antes de mais nada, acredito que vale a pena nós passarmos um pouco por algumas das arquiteturas conhecidas, que já foram bastante utilizadas ou estão mais em alta atualmente. Isso vai servir para entender bem o seu propósito e os pontos onde elas poderiam ser melhores, pensando sempre no mundo do desenvolvimento mobile.

MVC

Ilustração de como funciona a arquitetura MVC

A arquitetura MVC (Model View Controller) é uma das mais conhecidas no geral (não considerando apenas o universo mobile). Nela, explicando de maneira resumida, o Controller recebe os inputs do usuário e faz o processamento de todas as chamadas (com utilização das classes da camada Model para isto), chamando assim a camada de View para atualizá-la. Há também a possibilidade de, nesse modelo, utilizar algo similar ao padrão observer para atualizar a View em alguma atualização da camada Model.

Essa arquitetura inicialmente não foi elaborada pensando em desenvolvimento de aplicações móveis, e por isso traz algumas complexidades ao ser aplicada nesse contexto, como por exemplo os testes para a camada de lógicas e regras de negócio, pois elas muitas vezes são infladas (uma vez que é a única camada que possui esse tipo de conteúdo) e também por ela ser fortemente acoplada com a View, fazendo assim os testes se tornarem mais caros de serem executados e mais difíceis de serem construídos.

MVP

Ilustração de como funciona a arquitetura MVP

Baseado na arquitetura MVC mas desta vez elaborada pensada para mobile, o MVP (Model View Presenter) foi criado visando resolver alguns dos problemas citados anteriormente, assim facilitando o desenvolvimento. Nessa arquitetura, os inputs são recebidos diretamente na View, que só possui a responsabilidade de repassar o processamento para o seu Presenter. O Presenter por sua vez é responsável por fazer o processamento, utilizando e atualizando os dados do Model sempre que necessário. Um ponto importante a observar é que a View e o Presenter se conhecem mutuamente, pois a primeira recebe as requisições e só passa adiante enquanto a segunda realiza o processamento e chama os métodos da primeira para atualizar a interface do usuário.

Vale destacar que a principal diferença desta arquitetura para a anterior é que agora as camadas se conhecem única e exclusivamente por meio de interfaces. Com esse tipo de isolamento, o projeto se torna mais testável, porém ainda possuímos uma única camada de lógicas e regras de negócio.

MVVM

Ilustração de como funciona a arquitetura MVVM

Também baseada no modelo MVC e voltada para desenvolvimento mobile, a grande diferença do modelo MVVM (Model View ViewModel) para o anterior é que agora as camadas se conhecem através do padrão Observer, que nada mais é do que uma forma de realizar alterações de maneira reativa, observando a mudanças de estado de classes referentes ao processo que está sendo acompanhado.

De uma maneira geral, as camadas desta arquitetura tem características similares as características citadas no modelo MVP, reforçando que novamente temos apenas uma camada para lidar com as regras de negócio do nosso projeto.

Onde poderíamos aplicar melhorias?

As três arquiteturas citadas tem uma característica muito similar: possuir apenas uma camada de regras de negócio. E o que isso implica? Bem, em determinadas construções de projeto, podemos ter tanto o Controller, Presenter e View-Model inflados, com muita lógica e sem responsabilidade única (como por exemplo utilização de chamada a banco de dados, API externa, routers, tudo em uma mesma classe).

Isso nos traz, por exemplo, uma dificuldade se tentarmos aplicar o TDD (Test Driven Development), que prega que devemos realizar os testes antes de construir nossas funções de fato. Quando temos muitos artefatos para lidar e imaginar como deveriam funcionar em uma mesma camada, se torna automaticamente mais difícil de construir os casos de testes. Adicionamos muitas variáveis, como a necessidade de mais mocks, melhor organização dos testes para que se mantenham legíveis, e mais fluxos complexos em um mesmo arquivo de testes, podendo assim também inflá-los.

Clean Architecture ao resgate

Quando nos deparamos com um cenário onde notamos que temos modelos inflados e com muita responsabilidade, o mais natural a pensar é que precisamos realizar algum ajuste na construção do nosso projeto. Uma das alternativas aplicáveis neste contexto seria o Clean Architecture, que prega segundo suas regras que a arquitetura precisa ser:

  • Testável
  • Independente de frameworks
  • Independete de interface do usuário
  • Independete de banco de dados
  • Independete de qualquer agente externo

Com isso, criamos uma estrutura de camadas onde claramente temos camadas isoladas, com um nível de responsabilidades próprias e conhecimentos das outras camadas bem definidos.

Dependências entre as camadas do Clean Architecture

explicando rapidamente as camadas e seus limites de conhecimento, temos:

  • Presentation Layer: contém as classes de interface gráfica, que são gerenciadas pelas classes de regras de negócio que devem executar um ou mais casos de uso. Essa camada depende da Domain Layer.
  • Domain Layer: é a camada mais interna, que não depende de nenhuma outra camada. Ela contem entidades, casos de uso (regras de negócios) e interfaces para os repositórios. Os casos de uso combinam dados de uma ou mais interfaces de repositórios.
  • Data Layer: contém a implementação dos repositórios e uma ou mais fontes de dados. Tais repositórios são responsáveis por coordenar as mais diferentes fontes de dados existentes no projeto. Essa camada também depende do Domain Layer.

Tendo esses conceitos em mente, podemos seguir adiante e pensar como resolver nosso problema de arquitetura para aplicações móveis, que possui nuances específicas que são voltadas ao desenvolvimento Android

VIPER: Onde surgiu, o que faz, do que se alimenta?

Antes de falar da plataforma do nosso querido robozinho, vamos ter que voltar um pouco e contar como o VIPER chegou até ele, pois no início essa história mais tem haver com o iOS do que com o Android.

Anos atrás, a Apple adotava o modelo arquitetural MVC como padrão para que seus desenvolvedores pudessem seguir seus projetos, e dava total suporte para que essa decisão fosse adotada. Com o passar dos anos, a partir do momento em que os projetos foram se tornando de larga escala e as equipes cada vez maiores e distribuídas, os problemas referentes a essa arquitetura que citamos anteriormente começaram a aparecer e se tornaram uma grande dor de cabeça para todas as pessoas.

Dessa forma, buscando uma alternativa, uma nova arquitetura foi proposta, baseada inteiramente nos conceitos do clean architecture. Assim surgiu o VIPER (View Interactor Presenter Entity Router), que tem a seguinte estruturação:

Ilustração de como funciona a arquitetura VIPER

View: responsável por receber as entradas do usuário e redirecionar a quem sabe de fato lidar com ela, o seu Presenter. A View conceitualmente conhece seu único Presenter.

Presenter: o Presenter não sabe lidar com todo tipo de requisições. Algumas ele de fato vai dar conta de processar por si só, mas outras ele apenas sabe a quem enviar a depender do comando recebido. Essa camada tem conhecimento de quem é sua View, pois ele precisa atualizá-la. Essa camada também conhece a camada de Router e Interactor.

Router: é responsável por manter as funções de trocas de telas e troca de dados entre elas.

Interactor: é a camada responsável por fazer chamadas a fontes de dados, como banco de dados ou API externas. Essa camada conhece a Entity e faz uso da mesma para montar a resposta das requisições.

Entity: camada que contém a estrutura dos dados a ser utilizado na aplicação.

É interessante observar que aqui existe uma liberdade para escolher a forma de isolamento entre as camadas, que pode ser feita sem maiores dificuldades através de interfaces ou o padrão observer

Com esse formato genérico, baseado em um conceito agnóstico de plataforma porém pensado em desenvolvimento de aplicações móveis, podemos já notar uma similaridade e uma possibilidade de aplicação do VIPER também no Android. Mais adiante vou explorar um exemplo para que possamos entender na prática como funciona, de fato, essa arquitetura que tem um embasamento diferente de todas as anteriores que já passamos neste artigo.

Pontos fortes e pontos fracos

Antes de passarmos para o exemplo, acredito que vale a pena citarmos e entendermos o que o VIPER tem de nos oferecer de melhor e as suas fraquezas.

Pontos fortes

  • Possibilidade de reuso de módulos e sub-módulos
  • Maior facilidade em pensar em divisão de tarefas dentro de uma equipe com uma maior quantidade de pessoas
  • Impacto minimizado em mudanças realizadas no código
  • Separação do código em camadas com responsabilidades únicas
  • Maior facilidade para estruturar os testes

Pontos fracos

  • Necessidade de mais escrita de código (mais boilerplate)
  • Curva de aprendizado mais íngreme, pois a quantidade de conteúdos disponíveis em relação a essa arquitetura é menor em comparação a outras.

Um exemplo prático

Mostrando como de fato funcionaria a aplicação de uma arquitetura com estes conceitos explicados na plataforma Android, abaixo temos uma sequência de códigos de cada camada, ilustrando o que foi mostrado acima sobre cada uma delas.

A aplicação em si é bastante simples e com a finalidade apenas de aprendizado. Em curtas palavras, ele possui duas telas: na primeira é possível clicar em um botão para gerar uma frase aleatória e na segunda a frase é exibida, junto com um novo botão para gerar um novo texto.

As duas telas da aplicação de exemplo

Primeiro podemos começar pelo código da nossa View, que como comentei basicamente vai receber alguma ação do usuário e repassar para que a mesma seja processada mais adiante no Presenter.

Código da View da aplicação

Uma vez passada adiante, a requisição vai para o Presenter, que por sua vez precisa lidar com duas coisas: uma chamada a uma API externa e uma troca de tela com passagem de dados quando a primeira ação for completada. Para isso, com o seu conhecimento das camadas, ele faz uso do Interactor para a primeira ação (utilizando o padrão Observer) e do Router para a segunda, como mostra o código abaixo.

Código do Presenter da aplicação

Neste exemplo, o Router é apenas um arquivo Kotlin com as funções de transição e passagem de dados, como deve ser em sua essência.

Código do Router da aplicação

O Interactor neste exemplo apenas lida com a consulta de uma frase aleatória para uma API externa retornando um Observable. Por utilizarmos ReactiveX e Retrofit2 nesse exemplo e para fazermos uma separação mais clara de responsabilidades, existe um arquivo de serviço, que vai lidar puramente com o endpoint da API que estamos consultando e que vai fazer o relacionamento da resposta com a estrutura que montamos na camada de entidades. As classes Interactor e Service podem ser vistos nos exemplos abaixo.

Código do Interactor da aplicação
Código do Service da aplicação

Por fim, temos os arquivos da camada Entity, que nada mais são do que um mapa da resposta do serviço na nossa aplicação, para podermos usar os dados retornados como objetos manipuláveis.

Código de uma das entidades da aplicação

O projeto completo pode ser encontrado no repositório Viper Sample App, para que você possa ter uma visão geral de como está funcionando e do que foi utilizado em relação as ferramentas.

Conclusão: e a melhor arquitetura é…

Antes de eu precisar me explicar ou começar a receber uma chuva de pedras, já adianto que o essencial de entender depois de ler e aprender sobre qualquer nova tecnologia, framework, arquitetura, etc, é saber o seu propósito, pois cada uma pode ter um diferencial que se enquadre melhor em determinados contextos.

Com isso, deixo claro que não existe uma arquitetura que seja a bala de prata, ou que tenha características inteiramente exclusivas e que você não pode adaptar. Qualquer uma é adaptável a um contexto e é até saudável que isso seja feito para que seu projeto se mantenha com uma qualidade alta.

Assim, o VIPER vem como mais uma alternativa, que traz as vantagens de deixar uma composição de estrutura de projeto clara, limpa e direta. Ela também auxilia na aplicação do TDD como ferramenta de guia de desenvolvimento de código, o que é bastante interessante para manter a qualidade e a documentação do seu projeto como um todo.

--

--