Modularidade é um princípio pelo qual uma aplicação é montada a partir de módulos independentes, cada um responsável por uma única funcionalidade. De acordo com Android Developers, a divisão em módulos acelera a compilação através da compilação paralela e permite que as equipes trabalhem em diferentes partes da aplicação de forma independente. A arquitetura modular tornou-se o padrão para grandes projetos mobile com dezenas de desenvolvedores.
Pontos principais
Modularidade é uma forma de organizar código onde uma aplicação consiste em módulos fracamente acoplados, cada um fornecendo uma funcionalidade estritamente definida através de uma interface pública. Diferente da arquitetura monolítica onde todas as classes residem em um único projeto, a abordagem modular divide o código em unidades de compilação fisicamente independentes.
O principal objetivo da modularidade é o gerenciamento da complexidade. Um desenvolvedor pode focar em um módulo sem ter em mente toda a base de código. Cada módulo tem sua própria área de responsabilidade e pode ser desenvolvido, testado e implantado independentemente dos outros. Isso é especialmente valioso em projetos com 10+ desenvolvedores, onde o trabalho paralelo em um monolito leva a frequentes conflitos de mesclagem.
É importante distinguir modularidade de arquitetura em camadas. As camadas (Presentation, Domain, Data) dividem o código por critérios técnicos, enquanto os módulos dividem por critérios funcionais. Um módulo de “Perfil do usuário” pode conter suas próprias camadas internas. Na prática, a abordagem modular e a arquitetura em camadas são combinadas: cada módulo tem sua própria estrutura de três camadas.
Módulos de funcionalidade são o tipo mais popular de módulo. Cada tela ou grupo de telas relacionadas é separado em seu próprio módulo: Onboarding, Profile, Settings, Feed. Um módulo de funcionalidade contém tudo necessário para que a funcionalidade funcione: UI, lógica de negócio, camada de dados. Os limites do módulo são protegidos — outras funcionalidades não podem acessar suas classes internas.
Módulos centrais contêm infraestrutura comum: redes, banco de dados, análise, sistema de design. Eles não dependem de módulos de funcionalidade, mas os módulos de funcionalidade dependem deles. Essa separação garante que alterar um SDK de análise não afetará a camada de rede, e vice-versa. Módulos centrais são reutilizados entre funcionalidades sem duplicação de código.
Módulos compartilhados contêm código usado por múltiplas funcionalidades: modelos de dados, utilitários, constantes, Visualizações personalizadas. O principal problema dos módulos compartilhados é o risco de se tornar um depósito (“módulo miscelânea”) onde código heterogêneo se acumula ao longo do tempo. Regra: um módulo compartilhado deve ter um tema claro, por exemplo “shared-ui” ou “shared-models”.
No Android, módulos compartilhados são frequentemente separados em bibliotecas com o prefixo lib: lib-network, lib-database, lib-ui-components. No iOS, a mesma função é realizada por Swift Packages internos dentro de um Workspace. Na prática, as equipes limitam o número de módulos compartilhados a 3–5 para evitar criar uma rede excessiva de dependências que complique a compilação.
Módulos de teste separados permitem executar testes apenas para o módulo alterado sem executar toda a suíte de testes. Isso reduz o tempo do pipeline CI/CD de horas para minutos. O isolamento em nível de módulo garante SoC no nível de compilação: um módulo de camada de rede não pode importar acidentalmente bibliotecas de UI em seus testes.
Cada módulo deve ter uma API pública claramente definida. No Android, isso é alcançado através de modificadores de acesso e api vs implementation no Gradle. No iOS, através de modificadores de acesso public/internal e dependências gerenciadas via Package.swift. Reduzir a visibilidade ao mínimo necessário é uma prática chave do design modular.
Gradle suporta arquitetura modular nativamente: cada módulo é uma unidade de compilação separada com seu próprio arquivo build.gradle. Projetos Android usam uma combinação de um módulo de aplicação (app) e vários módulos de biblioteca. Módulos de biblioteca não podem ser executados como uma aplicação, mas podem ser publicados como AAR em um repositório.
Uma característica chave do Gradle é a compilação paralela de módulos independentes. Se os módulos A, B e C não dependem uns dos outros, o Gradle os compila simultaneamente usando todos os núcleos da CPU. Em projetos com 20+ módulos, isso reduz uma compilação completa de 15 para 3–5 minutos. Compilações incrementais de um módulo alterado levam segundos.
O Gradle fornece dois tipos de dependências entre módulos: api (transitivas) e implementation (não transitivas). A diferença é criticamente importante para a modularidade: implementation oculta dependências transitivas dos consumidores do módulo. Se o módulo :profile usa :networking através de implementation, os consumidores de :profile não sabem sobre :networking e não podem acessá-lo.
// settings.gradle — declaração de módulos
include ':app'
include ':feature:profile'
include ':feature:settings'
include ':core:network'
include ':core:database'
// build.gradle feature/profile — dependências do módulo
dependencies {
implementation project(':core:network')
implementation project(':core:database')
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0'
}
O código mostra a estrutura de um projeto Android modular. Settings.gradle lista todos os módulos, e o build.gradle de cada módulo de funcionalidade especifica apenas os módulos centrais que precisa. O sistema de compilação resolve automaticamente dependências transitivas e compila os módulos na ordem correta.
Swift Package Manager (SPM) é a ferramenta de modularidade padrão no iOS desde 2019. O SPM permite dividir uma aplicação em Swift Packages, cada um dos quais pode ser uma biblioteca ou executável. Um Package define módulos (targets) e suas dependências através do Package.swift. O SPM é integrado ao Xcode e não requer ferramentas adicionais.
CocoaPods continua sendo o principal gerenciador de dependências para bibliotecas de terceiros. Podfile e Podspec definem a estrutura modular, e o CocoaPods gera um workspace com projetos pod separados. Para a modularidade de seu próprio projeto, as equipes cada vez mais escolhem o SPM porque é integrado ao Xcode e não requer instalação.
Na modularidade do iOS, o controle de acesso desempenha um papel importante: public, package, internal, fileprivate e private. Um módulo publica apenas os tipos que devem ser acessíveis a outros módulos. Detalhes internos de implementação são ocultados por trás dos modificadores internal e private. Isso evita dependências ocultas entre módulos.
// Package.swift — estrutura modular de um projeto iOS
let package = Package(
name: "MyApp",
platforms: [.iOS(SupportedPlatform.iOSVersion.v17)],
products: [
.library(name: "ProfileFeature", targets: ["ProfileFeature"]),
.library(name: "NetworkCore", targets: ["NetworkCore"]),
],
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.9.0")
],
targets: [
.target(name: "ProfileFeature", dependencies: ["NetworkCore"]),
.target(name: "NetworkCore", dependencies: ["Alamofire"]),
]
)
Package.swift declara dois produtos de biblioteca: ProfileFeature e NetworkCore. ProfileFeature depende de NetworkCore mas não sabe da existência do Alamofire — ele está oculto dentro do NetworkCore. Esse isolamento é uma aplicação direta do SoC no nível do módulo: alterações no cliente HTTP não exigem recompilação do ProfileFeature.
A principal vantagem da modularidade é a velocidade de desenvolvimento. As equipes trabalham em paralelo em diferentes módulos sem conflitos de código. O pipeline CI/CD compila apenas os módulos alterados e executa apenas seus testes. O tempo de feedback diminui e a frequência de lançamentos aumenta. Spotify, Uber e Airbnb publicaram estudos de caso de migração para arquitetura modular com melhorias de métricas de 2 a 3 vezes.
A segunda vantagem é o isolamento de erros. Um bug no módulo Profile não afeta o módulo Payments se não houver dependências diretas entre eles. Isso é especialmente importante em aplicações com funcionalidades de alto risco (pagamentos, dados médicos), onde um erro em uma tela não relacionada não deve bloquear o lançamento de funcionalidade crítica.
O principal desafio é o gerenciamento de dependências. Com design inadequado, surge um grafo de módulos onde alterar um módulo causa a recompilação em cascata de dezenas de outros. A solução é seguir a regra de aciclicidade: o grafo de dependências de módulos deve ser um grafo acíclico direcionado (DAG). Ferramentas como Gradle Module Graph Assert ajudam a detectar ciclos em tempo de compilação.
O segundo desafio é o maior tempo de configuração inicial. Criar uma arquitetura modular requer mais tempo na fase de inicialização do projeto. Projetos pequenos com 1–3 desenvolvedores podem não se beneficiar da modularidade, gastando tempo mantendo limites de módulos sem real necessidade de paralelização. A solução é começar com um monolito e extrair módulos à medida que a equipe cresce.
A abordagem feature-first agrupa módulos por funcionalidade: cada tela ou grupo de telas se torna um módulo separado. A abordagem layer-first divide o código por critérios técnicos: módulos separados para UI, lógica de negócio e dados. Na prática, a maioria das equipes escolhe feature-first com módulos centrais — isso proporciona melhor isolamento e navegação clara do projeto.
A escolha entre abordagens depende do tamanho da equipe e da previsibilidade das funcionalidades. Se você sabe exatamente quais telas estarão no projeto, feature-first permite que cada desenvolvedor seja responsável por seu próprio módulo. Se a funcionalidade muda frequentemente e se sobrepõe entre telas, layer-first oferece mais flexibilidade na reutilização de código entre diferentes funcionalidades.
Perguntas frequentes
O número ótimo depende do tamanho do projeto e da equipe. Para uma equipe de 5 pessoas, 6–10 módulos são suficientes. Para 20+ desenvolvedores, 20–40 módulos. Regra: um módulo deve ser pequeno o suficiente para que um desenvolvedor o entenda completamente, e grande o suficiente para não criar uma rede excessiva de dependências.
A modularidade adequada acelera a compilação através de compilação paralela e cache. No entanto, um número excessivo de módulos com dependências estreitas diminui a compilação — Gradle e Xcode gastam tempo resolvendo o grafo. A chave para compilações rápidas é minimizar dependências transitivas e manter a aciclicidade.
Sim, mas iterativamente. Comece extraindo os módulos centrais (rede, banco de dados), depois extraia as funcionalidades uma por uma. Use feature flags para habilitar o novo código modular junto com o código monolítico antigo. A migração completa de uma aplicação grande leva de 3 a 12 meses.
Módulos são unidades de compilação dentro de uma única aplicação. Microsserviços são processos separados rodando em servidores diferentes. Módulos dividem código, microsserviços dividem runtime. No desenvolvimento mobile, o termo “microapps” é frequentemente usado como um híbrido: módulos de funcionalidade que podem rodar como aplicações independentes.
Cada módulo tem seus próprios testes unitários que rodam independentemente. Testes de integração verificam a interação entre módulos. Testes de UI cobrem módulos de funcionalidade com dados simulados. A arquitetura modular simplifica os testes: simular uma dependência de outro módulo é mais fácil do que simular parte de um monolito.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também