Pular para o conteúdo principal
Versão: Versão Mercados

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:

  1. Definir schema GraphQL
  2. Implementar resolvers
  3. 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