Arquitetura de aplicativo Flutter com Riverpod: uma introdução
Ao criar aplicativos complexos, escolher a arquitetura de aplicativo correta é crucial, pois permite estruturar seu código e dar suporte à sua base de código à medida que ela cresce.
Uma boa arquitetura deve ajudá-lo a lidar com a complexidade sem atrapalhar. Mas nem sempre é fácil acertar:
- A ausência dele cria uma falta geral de organização em seu código
- O uso excessivo dele leva ao excesso de engenharia, dificultando até mesmo mudanças simples
Na prática, as coisas podem ser bastante diferenciadas e é preciso alguma prática e experiência para obter o equilíbrio certo.
Portanto, neste artigo, apresentarei uma arquitetura de referência baseada no pacote Riverpod que é muito adequada para o desenvolvimento de aplicativos Flutter.
E também oferecerei uma comparação com outras arquiteturas populares, pois peguei emprestados muitos conceitos e ideias delas.
Conteudo
Visão geral das arquiteturas existentes
Se você pesquisar este tópico, poderá encontrar termos como MVC, MVP, MVVM e Clean Architecture. Essas são arquiteturas de aplicativos populares que foram introduzidas há muito tempo para resolver problemas semelhantes aos que enfrentamos hoje com o Flutter.
Estritamente falando, MVC, MVP e MVVM são padrões de design, enquanto a arquitetura limpa define um conjunto de regras e princípios para ajudá-lo a arquitetar qualquer sistema de software complexo.
Embora os princípios sobre os quais foram construídos ainda sejam muito relevantes hoje, eles não foram adaptados para o desenvolvimento de aplicativos Flutter.
Ainda assim, muitas tentativas foram feitas para trazê-los para o mundo Flutter, com vários graus de sucesso.
Arquiteturas populares de aplicativos do Flutter: Bloc e Stackend
Mais notavelmente, a Bloc Architecture ganhou uma boa adoção, principalmente graças à popularidade da Bloc Library, que é usada por várias grandes empresas. É muito opinativo e nos dá um conjunto estrito de regras sobre como devemos estruturar nossos aplicativos Flutter (e isso é bom!).
Outra promissora é a Stacked Architecture, que é baseada no pacote Stacked e é amplamente inspirada no MVVM.
Ambos dependem do Provider, que é o pacote recomendado para gerenciamento de estado na documentação oficial do Flutter.
E embora não haja nada de errado com Bloc ou Stacked, o autor de Provider criou o pacote Riverpod para “fazer algumas melhorias que de outra forma seriam impossíveis” (em suas próprias palavras).
Com o tempo, passei a apreciar o poder do Riverpod como uma solução completa para injeção de dependência e gerenciamento de estado. Então decidi construir minha própria arquitetura em cima disso. 👇
Minha opinião sobre a arquitetura do aplicativo Flutter (usando Riverpod)
Ao criar aplicativos Flutter de complexidade variada, experimentei muito com a arquitetura do aplicativo e desenvolvi uma compreensão profunda sobre o que funciona bem e o que não funciona.
O resultado é uma arquitetura de referência que usei em todos os meus projetos mais recentes.
Por falta de um nome melhor, chamarei isso de Riverpod Architecture – embora tenha em mente que esta é apenas minha opinião, e não uma arquitetura “oficial” endossada por Remi Rousselet (o autor de Riverpod).
Essa arquitetura é composta por quatro camadas (dados, domínio, aplicação, apresentação).
Aqui está uma prévia:
Cada uma dessas camadas tem sua própria responsabilidade e há um contrato claro de como a comunicação ocorre além das fronteiras.
No diagrama acima, as setas indicam as dependências entre as camadas.
Comparação com Arquitetura Limpa
A Clean Architecture foi introduzida por Robert C. Martin e é representada por este diagrama:
Embora as coisas tenham nomes diferentes, podemos identificar alguns conceitos semelhantes:
- Entidades → Modelos
- Casos de Uso → Serviços
Como a Clean Architecture foi projetada para ser independente da interface do usuário, ela não considera um fluxo unidirecional de dados das fontes de dados para a interface do usuário, que é um princípio básico de design em muitas arquiteturas de aplicativos modernos (Flutter e Web).
Em vez disso, ele usa uma camada de adaptador de interface que contém controladores, gateways e apresentadores. Enquanto na arquitetura Riverpod, os repositórios pertencem à camada de dados e os controladores pertencem à camada de apresentação.
Da mesma forma, a camada externa chamada de estruturas e drivers contém a interface do usuário e o banco de dados. Mas estes estão em extremos opostos na arquitetura do Riverpod.
Isso não quer dizer que a Arquitetura Limpa seja ruim. Mas acho que isso adiciona alguma sobrecarga mental e prefiro usar um modelo conceitual mais próximo do que uso diariamente para o desenvolvimento de aplicativos Flutter.
Comparação com MVC
Model–view–controller (MVC) é um padrão de projeto de software feito de 3 tipos de componentes:
- Model: gerencia diretamente os dados, a lógica e as regras do aplicativo
- View: componentes da interface do usuário (widgets)
- Controller: aceita entrada e a converte em comandos para o modelo ou visualização
O MVC normalmente sobrecarrega o modelo com muita lógica e uma camada de serviço adicional é frequentemente adicionada: MVC+S.
O modelo deve ser reativo (por exemplo, usando ChangeNotifier
) para que os widgets sejam reconstruídos quando ele for alterado.
E as entradas do usuário sempre passam pelo controlador e não pelo modelo diretamente.
Quando comparado com a arquitetura Riverpod, o vanilla MVP é bem diferente em como as camadas se comunicam entre si:
Comparação com MVVM
O padrão Model-View-ViewModel (MVVM) foi introduzido para separar a lógica de negócios e de apresentação de um aplicativo de sua interface de usuário (UI).
Geralmente é representado assim:
Ao usar o MVVM, a exibição não pode acessar o modelo diretamente.
Em vez disso, o modelo de exibição manipula as entradas do usuário e converte os dados do modelo para que possam ser facilmente apresentados.
E o modelo de exibição é conectado à exibição por meio de uma vinculação de dados (data binding) que usa o observer design pattern .
Ter um modelo de exibição intermediário é particularmente útil quando os dados retornados do back-end não mapeiam bem para a interface do usuário que você precisa mostrar.
A esse respeito, é fácil comparar o MVVM com a arquitetura Riverpod:
- View → Widget
- View Model → Controller
- Model → Model + repository + data source
MVVM é apenas um padrão de design e o diagrama acima não inclui nenhum conceito de dados posteriores.
Comparação com Bloc Arquitetura
A página Bloc Architecture contém este diagrama que define três camadas:
- Apresentação
- Logíca de negócios
- Dados (repositório e provedor de dados)
Isso está de acordo com a arquitetura Riverpod apresentada aqui:
- Apresentação (IU) → Widgets
- Lógica de Negócios (Bloc) → Controllers e Services
- Dados (Repositories) → Repositories
- Dados (Data Providers) → Data Sources
A principal diferença é que os blocos podem depender de outros blocos para reagir às suas mudanças de estado, enquanto a arquitetura Riverpod tem uma distinção mais clara entre controladores (que gerenciam o estado do widget) e serviços (que gerenciam o estado do aplicativo).
Observação: ao usar o Bloc, as alterações de estado são sempre representadas como fluxos de dados. Mas o Riverpod permite observar diferentes tipos de dados (Streams, Futures, StateNotifiers, ChangeNotifiers etc.)
No geral, acho que Bloc Arquitecture é a mais próxima da abordagem apresentada aqui.
Comparação com arquitetura empilhada
A arquitetura empilhada (baseada no pacote empilhado) é semelhante (mas não idêntica) ao MVVM e consiste em três partes principais que mapeiam facilmente a arquitetura Riverpod:
- View → Widgets
- ViewModel → Controller
- Service → Service
Ele também define algumas regras sobre o que você pode ou não fazer (mais informações aqui).
Stacked fornece uma arquitetura básica e um extenso conjunto de widgets úteis (como ViewModelBuilder
) que você pode usar para “ligar” um ViewModel com um View e facilitar sua vida para que você não precise reinventar a roda.
Os próprios ViewModels são classes Dart que estendem ChangeNotifier e vêm com variantes reativas e não reativas.
A principal diferença é que Stacked é baseado em Provider, enquanto a arquitetura Riverpod usa Riverpod (obviamente 😅).
Mas como dissemos, Riverpod nos dá tudo o que precisamos para construir uma arquitetura robusta.
Comparação com a arquitetura do aplicativo Android
Eu não mencionei isso até agora, mas os documentos do Android também têm um guia útil para a arquitetura do aplicativo que se parece muito com o que estou usando.
Embora essa arquitetura se refira a muitos componentes comuns do Android (como atividades, fragmentos e assim por diante), eles são iguais aos widgets no mundo do Flutter e há uma correspondência próxima entre as camadas:
Na arquitetura do aplicativo Android, a interface do usuário e as camadas de dados são sempre necessárias, enquanto a camada de domínio é opcional. Isso ocorre porque nem todos os aplicativos têm lógica de negócios complexa ou lógica de negócios simples que precisa ser reutilizada por vários modelos de exibição. Da mesma forma, na arquitetura do Riverpod faz sentido omitir a camada de aplicação se ela não for necessária.
No geral, recomendo que você leia todo o guia do Android, pois ele descreve o papel de cada camada em detalhes.
Outras Arquiteturas Populares
A arquitetura do aplicativo é baseada em abstrações implementadas usando padrões de design, não APIs. Como resultado, você pode criar sua própria arquitetura de aplicativo em cima de outros pacotes populares de gerenciamento de estado, como redux, MobX, etc.
Você pode até usar o que o Flutter oferece pronto para uso (InheritedWidget, ChangeNotifier, ValueListenableBuilder e amigos).
Observação: GetX é uma estrutura de gerenciamento de estado popular que afirma fazer tudo. Mas não vou cobrir isso aqui, por alguns motivos muito bons.
Em última análise, o que mais importa é que você defina contratos e limites claros em seu aplicativo.
A esse respeito, gosto muito desta citação do blog de engenharia do eBay:
Acreditamos que escolher a ferramenta certa ou aplicar um único padrão de projeto é muito menos importante do que estabelecer contratos e limites claros entre os recursos e componentes exclusivos do aplicativo. Sem limites, torna-se muito fácil escrever um código que não pode ser mantido e que não pode ser dimensionado à medida que o aplicativo cresce em complexidade.
Conclusão
Como vimos, a Riverpod Architecture tem muitas coisas em comum com outras arquiteturas populares (e algumas diferenças).