Desbloqueando a Busca do PicPay
Modularização por Feature e Clean Architecture na Prática
Desbloqueando a Busca do PicPay: Modularização por Feature e Clean Architecture na Prática

A evolução contínua de um aplicativo na escala do PicPay esbarra em desafios como o acoplamento excessivo e a complexidade arquitetônica, que tornam a manutenção lenta e arriscada.
A feature de Busca é um exemplo prático desses problemas, com uma arquitetura por camadas que dificulta a agilidade dos times.
Este artigo propõe uma solução baseada em Modularização por Feature e Clean Architecture para destravar o desenvolvimento da Busca. Analisamos os pontos de atrito atuais e detalhamos como a nova abordagem cria uma base mais escalável, sustentável e com baixo acoplamento.
Conceitos-Chave para a Proposta
Para entender a solução, é essencial revisar os pilares arquiteturais que a sustentam:
- Modularização por Feature: Organiza o código em módulos isolados por funcionalidade (Busca, Perfil, Pagamentos), garantindo que mudanças em uma “caixa” não afetem as outras. Isso acelera o desenvolvimento e o tempo de build.
- Clean Architecture (Arquitetura Limpa): Uma filosofia que visa proteger as regras de negócio centrais do sistema de detalhes técnicos periféricos (como banco de dados ou UI). O core do sistema se torna independente e altamente testável.
- Acoplamento: O nível de dependência entre as partes do código. O alto acoplamento é um problema, pois uma alteração em um lugar pode quebrar várias outras. A meta é o baixo acoplamento para componentes mais autônomos.
- Princípio da Segregação de Interfaces (SOLID — ISP): Defende que um cliente (módulo) não deve ser forçado a depender de funcionalidades que não utiliza. A preferência é por vários “contratos” (interfaces) pequenos e específicos em vez de um único contrato genérico.
O Cenário Atual: Busca e seus Problemas
A feature de Busca é vital, permitindo que o usuário encontre pessoas, serviços, features, FAQs e produtos. Para garantir a relevância, o backend personaliza e ordena os resultados com base no contexto.
Apesar de a recomendação do PicPay ser a modularização por feature, a Busca ainda utiliza a modularização por camadas. Esta estrutura criou dois principais pontos de atrito:
1. Complexidade de Configuração Exposta
O fluxo de navegação da Busca exige o objeto SearchConfig, que deveria ser flexível e parametrizável:
interface SearchNavigation {
fun navigate(context: Context, searchConfig: SearchConfig)
}
O Problema: O SearchConfig é um modelo complexo, com cerca de 10 propriedades (algumas descontinuadas e com nomes pouco sugestivos).
// Excesso de propriedades e alta carga cognitiva
@Parcelize
data class SearchConfig(
val shouldUseLocation: Boolean,
val type: String,
val limit: Int,
...
): Parcelable
Essa alta carga cognitiva faz com que os times ignorem a configuração e simplesmente copiem padrões, anulando a flexibilidade pretendida.
2. Empacotamento “Relaxado” e Alto Acoplamento
A arquitetura atual por camadas expõe o SearchRepository na camada de domínio para que a camada de dados possa implementá-lo:
// Camada de Domínio expõe o repositório
interface SearchRepository {
suspend fun search(searchRequest: SearchRequest): List<SearchResultSection>
}
Isso permite que outros módulos injetem o repositório diretamente, pulando a camada de Use Case. Segundo a Arquitetura Limpa, isso é chamado de empacotamento por camadas “relaxada”.
O Problema: O acesso direto ao repositório cria um acoplamento indesejado. Qualquer alteração no contrato do repositório ou em seus parâmetros afeta diretamente todos os módulos clientes. Além disso, o módulo acaba expondo mais funcionalidades do que o cliente realmente necessita.
Proposta de Solução: Um Novo Modelo de Arquitetura
Nossa solução aborda os problemas atuais em três frentes principais:
1. Modularização por Feature (O Novo Alicerce)
A mudança mais evidente é migrar da modularização por camada para a Modularização por Feature.
Esta estratégia alinha a arquitetura à recomendação do PicPay e garante o controle estrito sobre quais entidades e contratos são expostos publicamente. O módulo :app passará a depender apenas dos módulos :impl (implementação), auxiliando também na redução do tempo de build.
2. Princípio da Segregação de Interfaces e Empacotamento por Componente
Para resolver o problema do acoplamento e da exposição do repositório, aplicamos o Princípio da Segregação de Interfaces (ISP):
- Contrato Mínimo: Criamos a interface Searchable, que define apenas o comportamento essencial: buscar por um termo em texto.
interface Searchable {
// O cliente só precisa de um termo (query)
suspend fun search(query: String): List<SearchResultSection>
}
- Ponto de Acesso Único: Adotamos o Empacotamento por Componente (Capítulo 34 da Arquitetura Limpa). O Searchable se torna o ponto único de acesso ao núcleo da Busca. O módulo público expõe apenas o Searchable (o contrato), sem revelar detalhes internos de implementação (repositório, use case, etc.).
- Criação do Objeto: A SearcherFactory serve como um “balcão de atendimento”: o cliente informa o SearchContext (o que ele precisa), e a fábrica se encarrega de criar e configurar o objeto Searchable internamente. Isso simplifica o uso para quem a consome, exigindo apenas o contexto.
interface SearcherFactory {
fun createSearcher(context: SearchContext): Searchable
// ...
}
3. Configuração Centralizada por Contexto (O Segredo Didático)
A complexidade da configuração da Busca é inevitável, mas pode ser internalizada para simplificar o uso por outras features.
- Simplificação para o Cliente: Em vez de receber o objeto SearchConfig com várias propriedades, a SearcherFactory recebe apenas um enumerador: o SearchContext.
- Centralização: Criamos as interfaces complexas, como SearchContextConfig e SearchOriginConfig, internas ao módulo de Busca, garantindo que novas configurações não gerem novas dependências externas.
- O Papel da Fábrica: Uma fábrica interna (SearchContextConfigFactoryImpl) é responsável por mapear o SearchContext (simples, via Enum: FEATURE_A, FEATURE_B, etc.) para a configuração interna e complexa (SearchContextConfig).
// Uso simplificado pelo cliente
override fun createSearcher(context: SearchContext): Searchable {
val contextConfig = contextConfigFactory.fromContext(context)
return Searcher(
repository = searchRepository,
//...
)
}
- Trade-off Controlado: Essa centralização intencional, apesar de violar o Princípio Aberto/Fechado (O/C) — novos contextos exigem modificar a fábrica, traz um benefício maior: protege a integridade da feature ao impedir que módulos externos injetem configurações inválidas.
Conclusão
A migração para Modularização por Feature e a aplicação de princípios da Clean Architecture (ISP e Empacotamento por Componente) resultam em uma solução robusta.
Com esta abordagem:
- O core do negócio é protegido e a evolução do código se torna mais simples.
- O acoplamento entre módulos é drasticamente reduzido.
- A complexidade de configuração é internalizada (Configuração por Contexto), simplificando o uso para os times clientes.
O resultado final é um desenvolvimento mais ágil, seguro e escalável para a feature de Busca e para o aplicativo como um todo.
O código de exemplo desta proposta pode ser encontrado no repositório do GitHub.
Sobre o autor:
Natural de Blumenau (SC), pós-graduado em Engenharia de Software pela PUC-Rio e atuando na PicPay desde 2022. Desenvolvedor Android a 10 anos e atualmente me aventurando no desenvolvimento iOS e backend. Sinta-se à vontade para entrar em contato comigo através do LinkedIn para falar sobre arquitetura de software e mais.
Referências:
- Martin, Robert C. Arquitetura Limpa: O Guia do Artesão Para Estrutura e Design de Software. Alta Books, 2019.
- Android Developers. Padrões comuns para modularização de apps.
- The Clean Code Blog. Solid Relevance.
메타데이터
- post_id
- 535d4cd369cc
- slug
- desbloqueando-a-busca-do-picpay-535d4cd369cc
- url
- https://medium.com/inside-picpay/desbloqueando-a-busca-do-picpay-535d4cd369cc
- canonical_url
- https://medium.com/inside-picpay/desbloqueando-a-busca-do-picpay-535d4cd369cc
- author_url
- https://medium.com/@jonathangsilveira
- status
- ok
- fetched_at
- 2026-06-12 07:40:50