Padrões de Design Utilizados
Esta página documenta os principais padrões de design e arquitetura utilizados no Sistema Divino Alimento versão Mercados, facilitando a compreensão e manutenção do código.
Visão Geral
O projeto utiliza uma combinação de padrões de design consagrados tanto no backend quanto no frontend, promovendo código limpo, manutenível e escalável.
Padrões Arquiteturais
Arquitetura em Camadas (Layered Architecture)
O sistema é dividido em camadas bem definidas com responsabilidades específicas:
Estrutura:
Frontend (React/Next.js)
↓
API Layer (GraphQL)
↓
Backend (Node.js/Express)
↓
Data Layer (Sequelize/PostgreSQL)
Benefícios:
- Separação clara de responsabilidades
- Facilita testes em cada camada
- Permite mudanças independentes
- Melhora a manutenibilidade
Exemplo:
// Frontend faz query GraphQL
const { data } = useQuery(GET_MERCADOS);
// GraphQL resolve com service
const mercados = await mercadoService.listar();
// Service usa model
return await Mercado.findAll();
Service Layer Pattern
Toda a lógica de negócio é encapsulada em services, mantendo os resolvers GraphQL e controllers focados apenas em receber/enviar dados.
Estrutura:
// service/mercado-service.js
class MercadoService {
async criar(data) {
// Validações de negócio
if (!data.nome) {
throw new ValidationError('Nome é obrigatório');
}
// Lógica de negócio
const mercado = await Mercado.create(data);
// Ações adicionais
await this.notificarAdmins(mercado);
return mercado;
}
async listar(filtros) {
// Lógica de filtragem
const where = this.construirFiltros(filtros);
return await Mercado.findAll({ where });
}
}
Benefícios:
- Lógica de negócio centralizada
- Reutilizável por diferentes resolvers
- Facilita testes unitários
- Reduz duplicação de código
Repository Pattern
O Sequelize ORM atua como camada de repositório, abstraindo o acesso ao banco de dados.
Exemplo:
// Ao invés de SQL direto
// SELECT * FROM mercados WHERE ativo = true
// Usamos o ORM
const mercados = await Mercado.findAll({
where: { ativo: true },
include: [PontoEntrega]
});
Benefícios:
- Abstração do banco de dados
- Queries type-safe com TypeScript
- Migrations versionadas
- Relacionamentos automáticos
Padrões de API
GraphQL Schema First
O schema GraphQL define o contrato da API antes da implementação.
Processo:
- Definir schema GraphQL
- Implementar resolvers
- Conectar com services
Exemplo:
# schema/mercado.graphql
type Mercado {
id: ID!
nome: String!
descricao: String
ativo: Boolean!
pontoEntrega: [PontoEntrega!]!
}
type Query {
mercados(ativo: Boolean): [Mercado!]!
mercado(id: ID!): Mercado
}
type Mutation {
criarMercado(input: MercadoInput!): Mercado!
}
Benefícios:
- Documentação automática
- Type-safety end-to-end
- Validação automática de inputs
- Facilita desenvolvimento frontend
DataLoader Pattern
Otimização de queries GraphQL para evitar o problema N+1.
Problema N+1:
// Sem DataLoader: 1 query + N queries
const ciclos = await Ciclo.findAll(); // 1 query
for (let ciclo of ciclos) {
const mercado = await Mercado.findByPk(ciclo.mercadoId); // N queries
}
Solução com DataLoader:
// Com DataLoader: 1 query + 1 query em batch
const mercadoLoader = new DataLoader(async (ids) => {
const mercados = await Mercado.findAll({
where: { id: ids }
});
return ids.map(id => mercados.find(m => m.id === id));
});
// Uso no resolver
const mercado = await mercadoLoader.load(ciclo.mercadoId);
Benefícios:
- Reduz drasticamente número de queries
- Caching automático por request
- Melhora performance
- Transparente para o resolver
Padrões Frontend (React)
Component-Based Architecture
Componentes React reutilizáveis seguindo o princípio de responsabilidade única.
Hierarquia:
Pages (rotas)
└── Layouts (estrutura)
└── Features (funcionalidades)
└── UI Components (primitivos)
Exemplo:
// Componente primitivo
function Button({ children, ...props }) {
return <button {...props}>{children}</button>;
}
// Componente de feature
function MercadoCard({ mercado }) {
return (
<Card>
<CardHeader>{mercado.nome}</CardHeader>
<CardContent>{mercado.descricao}</CardContent>
<CardFooter>
<Button>Ver detalhes</Button>
</CardFooter>
</Card>
);
}
// Componente de página
function MercadosPage() {
const { data } = useMercados();
return (
<div>
{data.map(m => <MercadoCard key={m.id} mercado={m} />)}
</div>
);
}
Benefícios:
- Reutilização de código
- Fácil de testar
- Manutenção simplificada
- Composição flexível
Custom Hooks Pattern
Lógica reutilizável encapsulada em hooks customizados.
Exemplo:
// hooks/use-mercados.ts
function useMercados(filtros?: FiltrosMercado) {
const { data, loading, error } = useQuery(GET_MERCADOS, {
variables: { filtros }
});
const [criar] = useMutation(CRIAR_MERCADO, {
refetchQueries: ['GetMercados']
});
return {
mercados: data?.mercados || [],
loading,
error,
criar
};
}
// Uso no componente
function MercadosPage() {
const { mercados, loading, criar } = useMercados({ ativo: true });
if (loading) return <Loading />;
return <MercadosList mercados={mercados} />;
}
Benefícios:
- Lógica compartilhada entre componentes
- Separação de concerns
- Facilita testes
- Código mais limpo
Compound Components Pattern
Componentes que trabalham juntos com estado compartilhado.
Exemplo:
// Componente composto
function MercadoForm({ mercado, onSubmit }) {
return (
<Form onSubmit={onSubmit}>
<FormField name="nome" label="Nome" required />
<FormField name="descricao" label="Descrição" />
<FormField name="ativo" label="Ativo" type="checkbox" />
<FormActions>
<Button type="submit">Salvar</Button>
<Button variant="outline">Cancelar</Button>
</FormActions>
</Form>
);
}
Benefícios:
- API declarativa e intuitiva
- Flexibilidade na composição
- Estado compartilhado implícito
- Código mais legível
Container/Presenter Pattern
Separação entre lógica (container) e apresentação (presenter).
Exemplo:
// Container (lógica)
function MercadosContainer() {
const { mercados, loading, error, criar } = useMercados();
const [filtros, setFiltros] = useState({});
const handleCriar = async (data) => {
await criar({ variables: { input: data } });
};
return (
<MercadosPresenter
mercados={mercados}
loading={loading}
error={error}
filtros={filtros}
onFiltrosChange={setFiltros}
onCriar={handleCriar}
/>
);
}
// Presenter (UI)
function MercadosPresenter({ mercados, loading, onCriar }) {
if (loading) return <Loading />;
return (
<div>
<MercadosList mercados={mercados} />
<Button onClick={onCriar}>Novo Mercado</Button>
</div>
);
}
Benefícios:
- Componentes de UI mais puros
- Facilita testes de UI
- Reutilização de apresentação
- Lógica isolada
Padrões de Validação
Schema Validation (Zod)
Validação declarativa com schemas reutilizáveis.
Exemplo:
// lib/validators.ts
const mercadoSchema = z.object({
nome: z.string().min(3, 'Nome deve ter ao menos 3 caracteres'),
descricao: z.string().optional(),
ativo: z.boolean().default(true),
adminId: z.string().uuid('ID inválido')
});
// Uso no formulário
function MercadoForm() {
const form = useForm({
resolver: zodResolver(mercadoSchema)
});
return <form onSubmit={form.handleSubmit(onSubmit)}>...</form>;
}
Benefícios:
- Validação type-safe
- Mensagens de erro customizadas
- Reutilização de schemas
- Integração com React Hook Form
Padrões de Estado
Apollo Client Cache
Gerenciamento de estado global via cache do Apollo Client.
Exemplo:
// Leitura do cache
const { data } = useQuery(GET_MERCADO, {
variables: { id },
// Usa cache se disponível
fetchPolicy: 'cache-first'
});
// Atualização otimista
const [atualizar] = useMutation(ATUALIZAR_MERCADO, {
optimisticResponse: {
atualizarMercado: {
...mercado,
...updates
}
}
});
Benefícios:
- Cache automático
- Atualizações otimistas
- Sincronização automática
- Menos chamadas à API
Local State (useState/useReducer)
Estado local para interações de UI.
Exemplo:
function MercadoFilters() {
// Estado local simples
const [ativo, setAtivo] = useState<boolean | null>(null);
const [busca, setBusca] = useState('');
// Estado complexo com reducer
const [state, dispatch] = useReducer(filtrosReducer, initialState);
return <FilterForm filters={state} onChange={dispatch} />;
}
Padrões de Criação
Factory Pattern
Criação de objetos complexos (relatórios, exportações).
Exemplo:
// services/relatorio-service.js
class RelatorioService {
criar(tipo, dados) {
switch(tipo) {
case 'CSV':
return new RelatorioCSV(dados);
case 'PDF':
return new RelatorioPDF(dados);
case 'XLSX':
return new RelatorioXLSX(dados);
default:
throw new Error('Tipo não suportado');
}
}
}
// Uso
const relatorio = relatorioService.criar('CSV', ciclo);
await relatorio.gerar();
Benefícios:
- Encapsula criação complexa
- Facilita adicionar novos tipos
- Código mais limpo
- Single Responsibility
Builder Pattern (React Hook Form)
Construção incremental de formulários complexos.
Exemplo:
function CicloForm() {
const form = useForm<CicloInput>()
.setDefaultValues(initialValues)
.setValidation(cicloSchema)
.setSubmit(handleSubmit)
.build();
return <Form {...form} />;
}
Padrões de Tratamento de Erros
Error Boundary Pattern
Captura de erros em componentes React.
Exemplo:
function ErrorBoundary({ children }) {
return (
<ErrorBoundaryComponent
fallback={<ErrorPage />}
onError={(error) => console.error(error)}
>
{children}
</ErrorBoundaryComponent>
);
}
Try-Catch com Feedback
Tratamento de erros com feedback ao usuário.
Exemplo:
async function handleCriar(data) {
try {
await criar({ variables: { input: data } });
toast.success('Mercado criado com sucesso!');
} catch (error) {
toast.error(error.message || 'Erro ao criar mercado');
}
}
Padrões de Performance
Memoization (React.memo, useMemo, useCallback)
Otimização de renderizações.
Exemplo:
// Componente memoizado
const MercadoCard = memo(({ mercado }) => {
return <Card>{mercado.nome}</Card>;
});
// Valor computado memoizado
function MercadoStats({ mercados }) {
const total = useMemo(
() => mercados.reduce((sum, m) => sum + m.valor, 0),
[mercados]
);
return <div>Total: {total}</div>;
}
// Callback memoizado
function MercadosList({ mercados }) {
const handleClick = useCallback((id) => {
navigate(`/mercados/${id}`);
}, [navigate]);
return mercados.map(m => (
<MercadoCard key={m.id} onClick={handleClick} />
));
}
Lazy Loading
Carregamento sob demanda de componentes.
Exemplo:
// Lazy load de rotas
const MercadosPage = lazy(() => import('./pages/mercados'));
const ProdutosPage = lazy(() => import('./pages/produtos'));
function App() {
return (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/mercados" element={<MercadosPage />} />
<Route path="/produtos" element={<ProdutosPage />} />
</Routes>
</Suspense>
);
}
Anti-Patterns a Evitar
❌ Prop Drilling Excessivo
Problema:
// Evite passar props por muitos níveis
<A prop={x}>
<B prop={x}>
<C prop={x}>
<D prop={x} />
</C>
</B>
</A>
Solução: Use Context API ou estado global (Apollo Cache).
❌ Lógica de Negócio no Resolver
Problema:
// Evite lógica complexa no resolver
mercado: async (_, { id }, context) => {
const mercado = await Mercado.findByPk(id);
// ❌ Lógica de negócio aqui
if (mercado.ativo && mercado.admin) {
// validações complexas...
}
return mercado;
}
Solução: Mova para service layer.
❌ useState para Dados Remotos
Problema:
// Evite gerenciar dados remotos com useState
const [mercados, setMercados] = useState([]);
useEffect(() => {
fetch('/api/mercados')
.then(res => res.json())
.then(setMercados);
}, []);
Solução: Use Apollo Client (useQuery).
Próximos Passos
- Estrutura de Diretórios - Veja onde aplicar esses padrões
- Configuração do Ambiente - Configure seu ambiente para desenvolvimento
- Como Contribuir - Comece a contribuir seguindo esses padrões