Aula 02 · 70–90 minutos
Como estruturar uma aplicação front-end grande
Por que aplicações front-end grandes desorganizam com o tempo, os princípios que revertem isso — coesão, acoplamento, colocação e uma estrutura que grita o domínio — e o mapa do que vem a seguir neste macrotema.
Resultado e mapa da aula
Ao final, você conseguirá olhar para a árvore de pastas de uma aplicação front-end e diagnosticar, com vocabulário preciso, por que ela ficou difícil de mudar — e propor uma reestruturação defensável, não uma reorganização por gosto pessoal. Esta é a aula de abertura do macrotema "Arquitetura de aplicações front-end"; ela estabelece os princípios que as próximas cinco aulas aprofundam.
- Vemos por que uma aplicação front-end degrada estruturalmente à medida que cresce, mesmo sem nenhum bug novo.
- Aprendemos dois princípios centrais — alta coesão/baixo acoplamento e "screaming architecture" — para julgar qualquer estrutura de pastas.
- Comparamos, em um caso concreto, organizar por tipo de arquivo contra organizar por feature.
- Vemos onde colocation (manter arquivos relacionados fisicamente próximos) ajuda e onde ela para de ajudar.
- Praticamos reestruturando uma pasta real.
- Mapeamos o que este macrotema ainda vai cobrir: separação em camadas (aula 3), feature-based vs. layer-based (aula 4), monorepo vs. multirepo (aula 5), acoplamento e boundaries entre módulos (aula 6) e um estudo de caso completo (aula 7).
O problema real: a pasta que ninguém ousa tocar
Toda aplicação front-end começa pequena e organizada. Um mês depois de lançada, alguém pede "muda o texto do botão de finalizar compra" e a resposta deveria ser trivial. Mas, seis meses depois, essa mesma mudança de uma linha exige abrir components/CheckoutForm.tsx, hooks/useCheckout.ts, services/checkoutApi.ts e utils/formatPrice.ts — quatro arquivos, em quatro pastas, cada uma compartilhada por outras dez features que nada têm a ver com checkout. Ninguém decidiu isso de propósito. A estrutura só cresceu adicionando arquivos ao balde certo por tipo técnico, e o balde virou uma gaveta com centenas de itens de features completamente diferentes misturados.
Note que nenhum desses sintomas é um "bug": o código roda, os testes passam, a aplicação funciona no navegador. O problema é estrutural — mede-se pelo custo de mudar algo, não pela correção do que já existe. É exatamente esse tipo de problema que arquitetura de front-end existe para prevenir.
Dois princípios para julgar qualquer estrutura
Antes de comparar estruturas concretas, você precisa de critérios objetivos — senão a discussão vira preferência estética ("eu gosto mais assim"). Dois princípios, um clássico da engenharia de software e um específico de organização de código, resolvem isso.
Alta coesão, baixo acoplamento
Um módulo tem alta coesão quando tudo dentro dele existe para resolver o mesmo problema; tem baixo acoplamento quando depende do mínimo possível de outros módulos, e os outros módulos dependem do mínimo possível dele. Esses dois termos vêm do trabalho de Larry Constantine sobre estruturas de software nos anos 1970 e continuam sendo o vocabulário padrão para descrever por que um módulo é fácil ou difícil de mudar isoladamente (resumo histórico e definições — Wikipédia).
Aplicado a pastas: components/CheckoutForm.tsx, hooks/useCheckout.ts e services/checkoutApi.ts têm alta coesão de propósito (todos existem por causa do checkout) mas estão fisicamente dispersos — o oposto do que a coesão pede. A pasta utils/ tem o problema inverso: fisicamente é uma pasta só, mas dentro dela formatPrice.ts (checkout), formatDate.ts (perfil) e debounce.ts (busca) não têm nenhuma razão de negócio para estar juntos — é uma coleção de acoplamento acidental por proximidade, não por propósito.
Screaming architecture: a pasta deve gritar o domínio, não o framework
Em um artigo influente, Robert C. Martin argumenta que a estrutura de mais alto nível de um sistema deveria comunicar imediatamente do que o sistema trata — "isto é um sistema de saúde", "isto é um sistema de contabilidade" — e não qual framework foi usado para construí-lo. Um framework é uma ferramenta que a arquitetura usa, não o desenho da própria arquitetura (Robert C. Martin, "Screaming Architecture", 2011).
Traduzido para front-end: se a resposta para "o que essa pasta grita?" é components/, hooks/, services/, utils/ — ela está gritando categorias técnicas do React, não o negócio. Se a resposta é features/checkout/, features/catalog/, features/profile/ — ela grita o domínio: qualquer pessoa (mesmo sem saber React) entende que esse produto tem carrinho, catálogo e perfil. Esse é o teste rápido que você pode aplicar a qualquer projeto em segundos: abra a primeira camada de pastas e pergunte o que ela está gritando.
O caso concreto: mesma mudança, duas estruturas
Diagrama 1 — Mudar a regra de preço exibida no checkout de um e-commerce. Organizando por tipo de arquivo, a mudança toca quatro pastas compartilhadas por toda a aplicação. Organizando por feature, a mesma mudança toca uma única pasta, e a feature vizinha (catálogo) permanece intocada e sem risco de colisão.
A estrutura à esquerda não está "errada" por ser feia — ela está errada porque o limite de pasta não coincide com o limite de mudança. Toda vez que uma regra de negócio muda, o "raio de explosão" da alteração atravessa pastas que servem dezenas de outras features, aumentando a chance de duas mudanças não relacionadas colidirem no mesmo arquivo e forçando quem revisa o código a entender toda a aplicação para avaliar uma mudança pequena. A estrutura à direita coloca a fronteira de pasta exatamente onde está a fronteira de mudança de negócio: uma alteração no checkout produz um diff contido em features/checkout/, e uma pessoa de outro time olhando o catálogo nunca precisa nem abrir essa pasta.
Isso não é uma opinião isolada deste curso: é o princípio central de metodologias de organização de front-end amplamente adotadas, como Feature-Sliced Design, que padroniza a ideia de organizar por fatias de funcionalidade de negócio (com convenções próprias, incluindo um linter para verificar violação de camada) em vez de por tipo técnico de arquivo (documentação oficial — feature-sliced.design).
Colocation: manter perto o que muda junto
Colocation é o princípio de manter fisicamente próximos os arquivos que costumam mudar juntos, em vez de agrupá-los por semelhança técnica. A documentação oficial do React já recomendava isso mesmo antes de existirem convenções formais de "feature folder": ao invés de separar pastas genéricas por tipo, agrupe os arquivos por rota ou por funcionalidade de produto, e deixe componentes realmente compartilhados por toda a aplicação em uma pasta comum separada — o React não impõe uma estrutura obrigatória, mas orienta a colocar primeiro o que muda junto, e só extrair para um nível "comum" quando um arquivo passa a ser usado por mais de um lugar (React — Perguntas frequentes sobre estrutura de arquivos). O Next.js formaliza essa mesma ideia como parte do seu roteador de app: por padrão, arquivos podem ficar colocados dentro da pasta de uma rota sem virarem rota acidentalmente, e só arquivos com nomes especiais (como page e route) são de fato públicos (Next.js — estrutura de projeto, Next.js — colocation).
Colocation tem um limite importante: ela resolve "onde o arquivo mora", não "quem pode depender de quem". Duas features vizinhas, cada uma bem colocada dentro da sua própria pasta, ainda podem se acoplar indevidamente se uma importar detalhes internos da outra diretamente. Prevenir isso é o assunto da aula 6 deste macrotema (acoplamento e boundaries entre módulos) — por ora, o que importa é que colocation resolve organização física, não a disciplina de quem pode importar o quê, que é uma decisão arquitetural separada.
Alternativas que falham — e o que fazer no lugar
Organizar por tipo técnico "porque é assim que o framework ensina"
Muitos tutoriais de React, Vue ou Angular começam com components/, hooks/ ou services/ porque isso é didaticamente simples para um projeto de exemplo com cinco arquivos. O erro é manter essa estrutura conforme o produto cresce para dezenas de funcionalidades de negócio. A estrutura por tipo escala mal precisamente porque o volume de arquivos por pasta cresce junto com o número de features, e nenhuma pasta nunca "termina" — components/ sempre recebe mais um componente de qualquer feature nova.
Colocar tudo em uma única "feature" gigante
O oposto também falha: uma pasta features/app/ com tudo dentro não tem coesão nenhuma — ela só trocou o nome da bagunça. Feature, aqui, significa uma fatia de negócio com fronteira reconhecível (checkout, catálogo, perfil), não um contêiner genérico. Se uma pasta de feature cresce a ponto de conter sub-domínios claramente distintos (ex.: features/checkout/pagamento/ e features/checkout/entrega/), isso é sinal de que a fatia deveria ser dividida, não de que features são uma má ideia.
Duplicar código para não decidir onde ele mora
Diante da dúvida "esse componente é do checkout ou é compartilhado?", uma saída ruim é copiar o arquivo para as duas pastas "por segurança". Isso parece resolver o desconforto de decidir, mas cria duas fontes de verdade que divergem silenciosamente assim que uma delas é corrigida e a outra não. A decisão correta é: comece colocado dentro da feature que o criou; extraia para uma pasta compartilhada (ex.: shared/ ou um design system, tema da aula 10) apenas quando um segundo consumidor real aparecer — nunca por antecipação especulativa.
Reestruturar tudo de uma vez, no mesmo pull request que muda comportamento
Mover centenas de arquivos e mudar uma regra de negócio no mesmo diff torna impossível saber, se algo quebrar, se a causa foi a movimentação de arquivos ou a mudança de comportamento. Reestruturação e mudança de comportamento são duas categorias de risco diferentes e devem ser dois pull requests diferentes, na ordem: primeiro mover (sem alterar comportamento, com testes existentes passando), depois mudar a regra.
Prática guiada: reestruture uma pasta real
Raciocínio antes da resposta
Primeiro, identifique as features de negócio envolvidas: checkout (finalizar compra) e catálogo (buscar e listar produtos). Depois, para cada arquivo, pergunte "ele existe por causa de uma dessas features, ou é usado por qualquer feature futura, sem relação com o domínio de nenhuma delas em particular?". Button.tsx é um exemplo do segundo grupo: um botão genérico de UI não pertence a checkout nem a catálogo — ele é infraestrutura visual compartilhada (design system, tema da aula 10). debounce.ts também é do segundo grupo: é uma função utilitária de propósito geral, sem nenhuma regra de negócio dentro.
Uma reestruturação incompleta
Uma tentativa comum é criar as pastas de feature, mas mover só os componentes visuais para dentro delas, deixando hooks e services nas pastas antigas "porque são muito técnicos para ficar dentro de uma feature":
features/checkout/CheckoutForm.tsx
features/catalog/ProductCard.tsx, ProductGrid.tsx
hooks/useCheckout.ts (continua fora)
hooks/useProductSearch.ts (continua fora)
services/checkoutApi.ts (continua fora)
services/catalogApi.ts (continua fora)Isso mantém metade do problema original: mudar a regra de checkout ainda exige tocar features/checkout/ e hooks/ e services/ — três pastas em vez de quatro, uma melhora pequena que não resolve o "raio de explosão" nem o teste de screaming architecture. A causa raiz do erro é tratar "hook" e "service" como categorias de organização, quando elas são apenas categorias técnicas do React — o mesmo erro de nível mais alto, um degrau abaixo.
A reestruturação completa
features/checkout/CheckoutForm.tsx, useCheckout.ts, checkoutApi.ts, formatPrice.ts
features/catalog/ProductCard.tsx, ProductGrid.tsx, useProductSearch.ts, catalogApi.ts
shared/Button.tsx, debounce.tsCada arquivo migrou para dentro da feature cuja regra de negócio ele serve, independentemente de ser um componente visual, um hook ou uma chamada de API — o critério de agrupamento passou a ser "propósito de negócio", não "categoria técnica do framework". formatPrice.ts foi para dentro de checkout/ porque, neste caso, só o checkout formata preço; se o catálogo também precisasse formatar preço da mesma forma, a função entraria em shared/ no momento em que esse segundo consumidor aparecesse — não antes.
Verificação: depois de mover os arquivos, rode a suíte de testes existente sem alterar nenhuma asserção; ela deve continuar passando, porque comportamento não mudou, só localização. Em seguida, aplique o teste de screaming architecture: peça para alguém de fora do time olhar só os nomes de pasta de primeiro nível e dizer do que o produto trata. Se a resposta incluir "React" ou "hooks" em vez de "checkout" e "catálogo", a reestruturação ainda não terminou.
Perguntas de entrevista e de mercado
"Como você decide a estrutura de pastas de uma aplicação nova?" Não comece pela estrutura — comece perguntando quantas features de negócio distintas o produto já tem ou terá em breve. Para um protótipo de poucas telas, organizar por tipo pode ser aceitável temporariamente; para qualquer produto com mais de uma funcionalidade de negócio clara, comece por feature desde o primeiro dia, porque migrar depois custa mais do que começar certo.
"O que você faz quando não sabe se um arquivo é compartilhado ou pertence a uma feature?" Responda com a regra de "um segundo consumidor real": comece o arquivo dentro da feature que o criou; mova para uma pasta compartilhada só quando uma segunda feature precisar dele de verdade, nunca por antecipação. Cite o risco do oposto — abstrair e compartilhar cedo demais cria acoplamento entre features que não deveriam se conhecer.
"Qual é a diferença entre coesão e acoplamento, com um exemplo de pasta?" Coesão é o quanto o conteúdo de um módulo existe pelo mesmo motivo; acoplamento é o quanto módulos diferentes dependem um do outro. Dê o exemplo desta aula: utils/ tem baixa coesão (arquivos de propósitos de negócio diferentes misturados por semelhança técnica), enquanto features/checkout/ bem-feita tem alta coesão e baixo acoplamento com features/catalog/.
"O que é 'screaming architecture' e por que isso importa no front-end?" É a ideia de Robert C. Martin de que a estrutura de mais alto nível de um projeto deveria comunicar o domínio do produto, não o framework usado. No front-end, isso separa projetos onde a primeira camada de pastas é components/hooks/services (grita "React") de projetos onde ela é checkout/catalog/profile (grita o negócio) — e o segundo é o alvo, porque sobrevive até a uma troca de framework.
"Colocation resolve acoplamento entre módulos?" Não sozinho. Colocation decide onde um arquivo mora fisicamente; não impede que uma feature importe detalhes internos de outra. Uma estrutura por feature bem colocada ainda precisa de uma disciplina explícita sobre o que cada feature expõe para as demais — assunto da aula 6 deste macrotema.
Feche o modelo mental
Uma aplicação front-end grande não fica difícil de mudar por acidente: ela fica assim quando o limite de pasta deixa de coincidir com o limite de mudança de negócio. Alta coesão e baixo acoplamento dizem o que julgar; screaming architecture dá o teste rápido (o que a primeira camada de pastas grita?); colocation diz como manter o que muda junto fisicamente próximo, com uma regra clara sobre quando algo vira compartilhado. Os termos e critérios desta aula estão consolidados no glossário desta trilha para consulta rápida nas próximas aulas.
Próxima aula planejada: separação entre UI, domínio, estado e infraestrutura — como dividir o que está dentro de uma única feature.
Referências primárias para aprofundar
Os princípios de coesão e acoplamento remontam ao trabalho de Larry Constantine sobre estruturas de software, resumidos com atribuição histórica na entrada da Wikipédia sobre acoplamento em engenharia de software. "Screaming architecture" vem do artigo original de Robert C. Martin. A orientação de colocation por feature/rota está documentada nas Perguntas frequentes de estrutura de arquivos do React e na documentação oficial do Next.js sobre estrutura de projeto e colocation. A metodologia de organização por fatias de feature está formalizada em Feature-Sliced Design.
Consulte também as fontes fundamentais da trilha e o catálogo completo de recursos, disponíveis no repositório do curso.
Recupere da memória: 10 questões
Recupere da memória: 10 questões
Por que uma pasta organizada por tipo de arquivo tende a piorar conforme o produto cresce?
O que significa dizer que um módulo tem alta coesão?
No exemplo da aula, por que utils/ tem baixa coesão?
O que 'screaming architecture' propõe, segundo Robert C. Martin?
Qual é o teste rápido de screaming architecture que a aula sugere?
O que é colocation, no sentido usado nesta aula?
Colocation, sozinha, resolve o quê?
Quando um arquivo usado só por uma feature deve ser movido para uma pasta compartilhada?
Por que reestruturar pastas e mudar uma regra de negócio no mesmo pull request é arriscado?
Na reestruturação incompleta do exercício guiado, qual erro persiste mesmo depois de criar pastas de feature para os componentes visuais?