Arquitetura Frontend Agnóstica de Framework
Este documento define os princípios arquitetônicos para construção de aplicações frontend modernas. A arquitetura utiliza uma abordagem modular e orientada por funcionalidades para melhorar a escalabilidade e manutenibilidade, independentemente do framework específico (por exemplo, React, Vue, Solid ou Svelte).
Convenção de Nomenclatura de Arquivos: Use kebab-case para todos os nomes de arquivos e diretórios (por exemplo, my-component.js ou user-profile/). Os nomes dos componentes no código devem seguir a convenção padrão para o framework ou biblioteca escolhida (por exemplo, PascalCase).
Diretório Raiz: src/
O diretório src/ contém todo o código fonte da aplicação.
1. domains/ - Módulos de Funcionalidades
O diretório domains/ contém as funcionalidades da aplicação. Cada sub-diretório em domains/ é um módulo de funcionalidade auto-contido que encapsula toda a lógica para um domínio de negócio específico (por exemplo, user-profile/ ou product-listing/). Esta estrutura permite melhor separação de código e permite que equipes trabalhem em funcionalidades independentemente.
1.0. Estrutura de Domínio
Cada pasta de funcionalidade encapsula todo o código para essa funcionalidade. Coloque arquivos de teste em sub-diretórios __tests__ dentro do diretório do código sendo testado. Um domínio típico pode incluir alguns ou todos os seguintes sub-diretórios. Crie essas pastas somente quando necessário.
README.md: Explica o propósito do domínio, principais responsabilidades, regras de negócio e interações.assets/: Recursos estáticos específicos da funcionalidade organizados por tipo:assets/images/(opcional): Imagens específicas do domínio, ilustrações e gráficos.assets/icons/(opcional): Ícones específicos da funcionalidade e recursos SVG.assets/fonts/(opcional): Recursos tipográficos específicos do domínio.assets/videos/(opcional): Conteúdo multimídia relacionado à funcionalidade.assets/documents/(opcional): PDFs específicos do domínio, guias ou arquivos de documentação.
Nota: Os recursos do domínio seguem o mesmo padrão organizacional dos recursos globais (veja seção 4), mas contêm conteúdo específico apenas para aquela funcionalidade. Crie subpastas de recursos somente quando você tiver múltiplos recursos daquele tipo. Domínios simples podem colocar todos os recursos diretamente na pasta
assets/.components/: Componentes de UI usados apenas dentro desta funcionalidade.logic/: Módulos contendo lógica de estado ou de negócio. Para domínios complexos, isso pode ser dividido em application/ e domain/ como descrito abaixo.views/: Componentes de apresentação de alto nível. Uma rota renderiza uma view.[feature-name]-routes.js: Definições de rotas para a funcionalidade (para roteamento baseado em configuração).store/(opcional): Módulos de gerenciamento de estado específicos da funcionalidade.types/(opcional): Definições de tipos TypeScript específicas do domínio.translations/(opcional): Arquivos de internacionalização para a funcionalidade.utils/(opcional): Funções auxiliares usadas apenas dentro da funcionalidade.layouts/(opcional): Componentes de layout usados apenas por views dentro desta funcionalidade.
1.1. Avançado: Separando Lógica de Aplicação e Lógica de Domínio
Para domínios com complexidade significativa de negócio, é altamente recomendado separar ainda mais a lógica em duas categorias distintas: Lógica de Aplicação e Lógica de Domínio (Modelos). Este é um princípio fundamental do Domain-Driven Design (DDD) que melhora muito a testabilidade e clareza.
- Lógica de Domínio (
logic/domain/oulogic/models/): Este é o núcleo da sua funcionalidade. Contém regras de negócio e comportamentos puros e agnósticos de framework. Um modelo é uma representação de software de um conceito do mundo real (ex., um ShoppingCart ou um User). Ele não sabe nada sobre a UI, APIs ou o framework escolhido. - Lógica de Aplicação (
logic/application/oulogic/use-cases/): Esta camada orquestra a lógica de domínio. Atua como ponte entre a UI e os modelos de domínio. Hooks, controllers ou composables ficam aqui. São responsáveis por lidar com entrada do usuário, buscar dados e chamar métodos nos modelos de domínio.
Exemplo: Um Domínio de Carrinho de Compras
- O Modelo de Domínio (
domains/shopping-cart/logic/domain/cart.js)
Esta classe pura representa a ideia de um carrinho e aplica suas regras.
// Este é o "modelo". É pura lógica de negócio.
export class Cart {
constructor(items = []) { this.items = items; }
addItem(newItem) {
if (newItem.stock < 1) { throw new Error("Não é possível adicionar itens fora de estoque."); }
this.items.push(newItem);
}
calculateTotal() { return this.items.reduce((total, item) => total + item.price,
0); }
}- A Lógica de Aplicação (
domains/shopping-cart/logic/application/use-cart.js)
Este hook React usa o modelo para conectá-lo à aplicação.
// A Lógica de Aplicação (domains/shopping-cart/logic/application/use-cart.js)
// Este hook React usa o modelo para conectá-lo à aplicação.
import { Cart } from `../domain/cart.js`;
import { api } from `../../../services/api.js`;
import { useState, useEffect } from 'react';
export function useCart() {
const [cartModel, setCartModel] = useState(new Cart());
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsLoading(true);
api.get(`/cart`).then(data => setCartModel(new Cart(data.items))).finally(() => setIsLoading(false));
}, []);
function handleAddItemToCart(item) {
const newCart = new Cart([...cartModel.items]);
try {
newCart.addItem(item); // Use a lógica de negócio do modelo
setCartModel(newCart);
} catch (error) { alert(error.message); }
}
return { cart: cartModel, isLoading, handleAddItemToCart };
}1.2. Compartilhamento de Código Entre Domínios: Um Anti-Padrão
Importar código diretamente entre domínios é um anti-padrão. Isso cria acoplamento forte e prejudica a modularidade.
A Solução: Promover Código Compartilhado
Se a lógica de um domínio for necessária em outro, promova-a para um diretório global (src/utils/, src/logic/, etc.) e faça ambos os domínios importarem de lá.
1.3. Comunicação Entre Domínios: Um Anti-Padrão
Assim como importações diretas de código são proibidas, comunicação direta em tempo de execução (ex., um domínio chamando uma função em outro) também é um anti-padrão.
A Solução: Mediar com um Event Bus Global
Domínios devem se comunicar indiretamente através de um event bus global (um sistema publish/subscribe), provavelmente definido em src/services/event-bus.js.
- Publish: Um domínio envia um evento para o bus, sem saber quem está ouvindo.
- Subscribe: Outros domínios ouvem eventos pelos quais se interessam.
Isso garante que os domínios permaneçam completamente desacoplados.
2. ui/ - Componentes de UI Globais
A biblioteca central para componentes de UI globalmente reutilizáveis. Não use barrel files (index.js).
ui/core/: Blocos de construção atômicos e abstratos (ex., button, input).ui/patterns/(Opcional): Componentes compostos para tarefas comuns e opinativas de UI (ex., confirm-modal).ui/lab/(Opcional): Componentes experimentais ou altamente especializados.
3. Roteamento
- Baseado em Configuração (React Router, Vue Router): Um diretório central
router/agrega configurações de rotas do arquivo[feature-name]-routes.jsde cada domínio. - Baseado em Arquivos (Next.js, SvelteKit): Um diretório de nível superior
src/pages/ousrc/routes/define a estrutura de URL. Esses arquivos devem ser enxutos, buscando dados e renderizando uma view do diretóriodomains/relevante.
4. Outros Diretórios Globais
layouts/: Templates de estrutura de página global (ex., app shell).assets/: Recursos estáticos globais organizados por tipo:assets/images/: Logos da marca, backgrounds de hero, ilustrações globais e imagens placeholder.assets/fonts/: Recursos tipográficos como web fonts (arquivos.woff2,.woff,.ttf).assets/icons/: Sistemas de ícones incluindo sprites SVG, arquivos favicon e recursos de ícones globais.assets/videos/: Vídeos da marca, vídeos de fundo e outros conteúdos multimídia.assets/documents/: PDFs globais, conteúdo baixável e arquivos de documentação.
styles/: Folhas de estilo globais, resets CSS e design tokens (theme.css).logic/: Lógica de negócio ou de estado global.utils/: Funções utilitárias globais e sem estado.services/: Serviços globais e compartilhados que lidam com preocupações transversais ou interações de infraestrutura. Exemplos incluem clientes de API, um event bus global, serviços de log ou wrappers de API do navegador.store/: Gerenciamento de estado global.types/: Tipos TypeScript compartilhados globalmente.
5. Testes e Documentação
- Co-localização: Coloque testes unitários em um sub-diretório
__tests__/. Coloque arquivosREADME.mddentro de qualquer pasta significativa para explicar seu propósito. - Nível de Projeto: Use um
tests/na raiz para testes E2E e umdocs/para documentação de alto nível. O diretóriodocs/deve conter:architecture.md: Este documento.onboarding-guide.md: Instruções para novos desenvolvedores.adr/: Um diretório para Registros de Decisão Arquitetônica (ADRs).
6. Relacionamento com Arquitetura de Slice Vertical
Esta arquitetura é uma aplicação direta da filosofia da Arquitetura de Slice Vertical (VSA), adaptada para desenvolvimento frontend moderno. Em vez de organizar código por camadas técnicas (ex., uma pasta global components/, uma pasta global hooks/), organiza código por funcionalidade.
- Alta Coesão: Todo o código necessário para uma única funcionalidade (a UI, lógica, recursos) fica junto em um lugar (
domains/[feature-name]/), tornando fácil raciocinar sobre e modificar. - Baixo Acoplamento: Regras rígidas contra comunicação direta inter-domínios garantem que funcionalidades são independentes e podem ser desenvolvidas em paralelo sem interferir umas com as outras.
- Adaptação Frontend: Em um contexto backend, um slice vertical vai do endpoint da API até o banco de dados. Nesta arquitetura frontend, um slice vai da interface do usuário (a URL e a view) até a camada de cliente da API (
services/api/).
7. Glossário
- ADR (Registro de Decisão Arquitetônica): Um documento que captura uma decisão arquitetônica importante, seu contexto e as consequências. ADRs fornecem um registro histórico de por que o sistema é construído da maneira que é.
- Anti-Padrão: Uma resposta comum a um problema recorrente que parece ser uma boa solução, mas no final cria mais problemas.
- Lógica de Aplicação: A camada que orquestra modelos de domínio e os conecta à UI e infraestrutura. Frequentemente implementada como hooks ou controllers.
- Barrel File: Um único arquivo
index.jsque exporta todos os outros módulos em um diretório. Desencorajado nesta arquitetura para melhorar o tree-shaking. - Co-localização: A prática de colocar arquivos relacionados juntos no mesmo diretório. Nesta arquitetura, testes (
__tests__/) e documentação (README.md) são co-localizados com o código que descrevem. - Coesão: O grau até o qual os elementos dentro de um módulo pertencem juntos. Esta arquitetura visa alta coesão agrupando todo código relacionado a uma única funcionalidade dentro da mesma pasta de domínio.
- Componente Core: Um componente de UI global de
ui/core/que é atômico, abstrato e altamente reutilizável (ex., button, input). - Acoplamento: O grau até o qual um módulo depende de outro. Esta arquitetura visa baixo acoplamento proibindo comunicação direta entre domínios.
- Domínio / Funcionalidade: Um slice vertical auto-contido da aplicação correspondente a uma capacidade de negócio específica.
- Event Bus: Um mecanismo global publish-subscribe que permite diferentes partes de uma aplicação (como domínios) se comunicarem sem estarem diretamente cientes umas das outras.
- Módulo Global: Um módulo localizado diretamente sob
src/que fornece funcionalidade compartilhada e transversal para toda a aplicação (ex.,ui/,utils/,services/). - Modelo (Modelo de Domínio): Uma representação de software pura e agnóstica de framework de um conceito de negócio do mundo real (ex., uma classe ShoppingCart). Contém dados e as regras de negócio que se aplicam a ele.
- Componente Pattern: Um componente de UI global de
ui/patterns/que é composto de vários componentes core para resolver uma tarefa comum e opinativa de UI (ex., confirm-modal). - Arquitetura de Slice Vertical: Um padrão arquitetônico onde código é organizado por funcionalidade (um “slice vertical”) ao invés de por camada técnica (uma “camada horizontal”).
- View: Um componente de apresentação de alto nível, tipicamente em
domains/[feature-name]/views/. O trabalho primário de uma rota é renderizar uma view.
8. Resumo de Escopos de Diretórios
- Escopo de Funcionalidade (dentro de
domains/[feature-name]/): Todos os diretórios relacionados à operação de uma funcionalidade (components/,logic/,views/, etc.). - Escopo Global (filhos diretos de
src/):ui/,router/,layouts/,assets/,styles/,logic/,utils/,services/,store/etypes/. - Nível de Projeto (raiz): Um diretório
docs/para documentação de alto nível e um diretóriotests/para testes E2E/integração.
9. Princípios Arquitetônicos Chave
- Modularidade: Organização orientada por domínio suporta desenvolvimento paralelo.
- Reutilização: Diretórios globais promovem princípios DRY para lógica e UI comuns.
- Separação Clara: Escopos explícitos para lógica específica de funcionalidade, global, de aplicação e de domínio minimizam a carga cognitiva.
- Manutenibilidade: Testes e documentação co-localizados melhoram qualidade de código e longevidade.
- Independência de Framework: Estes princípios são adaptáveis a qualquer framework frontend moderno.
- Autonomia de Equipe: Fronteiras claras empoderam equipes a possuírem funcionalidades com interferência reduzida.