Arquitetura do Sistema
Esta página descreve a arquitetura técnica da plataforma Divino Alimento - Versão Mercados.
Visão Geral
A plataforma Divino Alimento é uma aplicação web full-stack que facilita a distribuição de produtos em múltiplos mercados. O sistema utiliza uma arquitetura híbrida que combina:
- Backend legado com renderização server-side (Express + EJS)
- Frontend como SPA (React + TypeScript)
- APIs REST (legado) e GraphQL
Stack Tecnológico
Backend
Core
- Runtime: Node.js
- Framework: Express.js ^4.21.2
- Template Engine: EJS ^3.1.6 (views legadas)
- ORM: Sequelize ^6.37.1
- Database: PostgreSQL (produção), SQLite (testes)
APIs
- GraphQL: graphql ^16.11.0
- GraphQL HTTP: graphql-http ^1.22.4
- GraphQL IDE: Ruru ^2.0.0-beta.30
Autenticação
- OAuth2/OIDC: express-openid-connect ^2.4.0
- Session Management: Model Session com tokens
Utilitários
- PDF Generation: PDFKit ^0.12.1
- CSV Export: json2csv ^5.0.7
- Environment Config: dotenv ^9.0.1
Desenvolvimento
- Auto-reload: Nodemon ^2.0.7
- Migrations: Sequelize CLI ^6.6.2
Frontend
Build & Tooling
- Build Tool: Vite ^5.4.19
- Language: TypeScript ^5.8.3
- Linting: ESLint ^9.32.0
- Package Manager: Bun (lockfile presente)
Framework & Bibliotecas
- UI Framework: React ^18.3.1
- React DOM: ^18.3.1
- Routing: React Router DOM ^6.30.1
UI Components
- Component Library: shadcn/ui (headless)
- Primitives: Radix UI (30+ componentes)
- Accordion, Alert Dialog, Avatar, Checkbox
- Dialog, Dropdown Menu, Select, Tabs
- Toast, Tooltip, e muitos outros
- Icons: Lucide React ^0.462.0
Styling
- CSS Framework: Tailwind CSS ^3.4.17
- Typography: @tailwindcss/typography
- Utilities:
- clsx ^2.1.1
- tailwind-merge ^2.6.0
- class-variance-authority ^0.7.1
- tailwindcss-animate ^1.0.7
State Management
- Server State: TanStack React Query ^5.83.0
- Client State: React Context API
- GraphQL Client: graphql-request ^7.3.1
Forms & Validation
- Forms: React Hook Form ^7.62.0
- Resolvers: @hookform/resolvers ^3.10.0
- Validation: Zod ^3.25.76
Componentes Especializados
- Date Picker: React Day Picker ^8.10.1
- Date Utils: date-fns ^3.6.0
- Charts: Recharts ^2.15.4
- Carousel: Embla Carousel React ^8.6.0
- Notifications: Sonner ^1.7.4
- Themes: Next Themes ^0.3.0
- Drawer: Vaul ^0.9.9
- OTP Input: input-otp ^1.4.2
- Panels: react-resizable-panels ^2.1.9
Testing
Frameworks
- BDD: Cucumber ^12.2.0
- Unit: Jest (configurado)
- E2E: Mocha ^11.7.4
- Assertions: Chai ^4.3.6
- Test Data: @faker-js/faker ^10.0.0
DevOps
- Containerização: Docker & Docker Compose v2
- Automação: Rake (Ruby-based)
- Mock Auth: Mock OAuth2 Server (desenvolvimento)
Estrutura do Projeto
DivinoAlimento/
├── app/ # Backend Node.js
│ ├── src/
│ │ ├── server.js # Servidor Express
│ │ ├── routes.js # Definição de rotas
│ │ ├── api-graphql.js # Resolvers GraphQL
│ │ ├── api.graphql # Schema GraphQL
│ │ │
│ │ ├── controllers/ # Controllers REST
│ │ │ ├── CicloController.js
│ │ │ ├── CestaController.js
│ │ │ ├── ProdutoController.js
│ │ │ ├── OfertaController.js
│ │ │ ├── ComposicaoController.js
│ │ │ ├── PedidoConsumidoresController.js
│ │ │ ├── UsuarioController.js
│ │ │ ├── RelatoriosController.js
│ │ │ └── ...
│ │ │
│ │ ├── model/ # Business Logic
│ │ │ ├── Ciclo.js
│ │ │ ├── Produto.js
│ │ │ ├── Usuario.js
│ │ │ └── ...
│ │ │
│ │ ├── services/ # Service Layer
│ │ │ ├── services.js
│ │ │ └── ...
│ │ │
│ │ ├── db/ # SQL Queries
│ │ │ ├── cicloSql.js
│ │ │ ├── produtoSql.js
│ │ │ └── ...
│ │ │
│ │ └── views/ # Templates EJS (legado)
│ │ ├── index.ejs
│ │ ├── ciclo.ejs
│ │ └── parts/
│ │
│ ├── models/ # Sequelize Models
│ │ ├── index.js
│ │ ├── usuario.js
│ │ ├── ciclo.js
│ │ ├── produto.js
│ │ ├── oferta.js
│ │ ├── cesta.js
│ │ ├── session.js # Sessões GraphQL
│ │ └── ...
│ │
│ ├── migrations/ # Database Migrations
│ │ ├── 20210513-create-produto.js
│ │ ├── 20250923-create-sessions.js
│ │ └── ...
│ │
│ ├── features/ # BDD Tests (Cucumber)
│ │ ├── ciclo.feature
│ │ ├── usuario.feature
│ │ ├── sessions.feature
│ │ └── step_definitions/
│ │
│ ├── config/ # Configurações
│ │ └── config.js
│ │
│ ├── public/ # Assets estáticos
│ │
│ ├── package.json
│ ├── cucumber.js
│ └── jest.config.js
│
├── frontend/ # Frontend React
│ ├── src/
│ │ ├── main.tsx # Entry point
│ │ ├── App.tsx # Routing principal
│ │ │
│ │ ├── pages/ # Páginas
│ │ │ ├── Index.tsx # Homepage
│ │ │ ├── Login.tsx
│ │ │ ├── Register.tsx
│ │ │ ├── Dashboard.tsx
│ │ │ ├── Cesta.tsx
│ │ │ ├── Resumo.tsx
│ │ │ │
│ │ │ ├── admin/ # Área administrativa
│ │ │ │ ├── AdminDashboard.tsx
│ │ │ │ ├── AdminMercados.tsx
│ │ │ │ ├── AdminProdutos.tsx
│ │ │ │ ├── AdminEstoque.tsx
│ │ │ │ └── ...
│ │ │ │
│ │ │ └── fornecedor/ # Área do fornecedor
│ │ │ ├── FornecedorLogin.tsx
│ │ │ ├── LojaProdutor.tsx
│ │ │ ├── PedidosAberto.tsx
│ │ │ └── ...
│ │ │
│ │ ├── components/ # Componentes
│ │ │ ├── ui/ # shadcn/ui components
│ │ │ │ ├── button.tsx
│ │ │ │ ├── card.tsx
│ │ │ │ ├── dialog.tsx
│ │ │ │ └── ... (50+)
│ │ │ │
│ │ │ └── layout/ # Layout components
│ │ │ ├── AppShell.tsx
│ │ │ ├── ResponsiveLayout.tsx
│ │ │ ├── AdminSidebar.tsx
│ │ │ └── ...
│ │ │
│ │ ├── hooks/ # Custom Hooks
│ │ │ ├── graphql.ts # GraphQL queries/mutations
│ │ │ ├── useCycle.tsx
│ │ │ └── use-mobile.tsx
│ │ │
│ │ ├── contexts/ # React Context
│ │ │ └── ConsumerContext.tsx
│ │ │
│ │ ├── assets/ # Imagens, ícones
│ │ │
│ │ └── lib/ # Utilitários
│ │
│ ├── public/ # Static assets
│ ├── index.html
│ ├── vite.config.ts
│ ├── tailwind.config.ts
│ ├── tsconfig.json
│ └── package.json
│
├── mock-oauth2-server/ # OAuth Mock (dev)
├── compose.live.yml # Docker Compose
├── compose.tests.yml # Docker Compose Tests
├── Rakefile # Rake tasks
├── env.example # Environment template
└── README.md
Arquitetura em Camadas
Backend
1. Camada de Apresentação
REST (Legado)
- Templates EJS renderizados server-side
- Bootstrap 5 + jQuery
- Formulários tradicionais
GraphQL (Moderno)
- Schema definido:
/app/src/api.graphql - Endpoint:
/graphql - IDE integrado: Ruru
2. Camada de Controle
REST Controllers
// app/src/controllers/CicloController.js
class CicloController {
async showIndex(req, res) {
const ciclos = await Ciclo.getAll();
res.render('ciclo-index', { ciclos });
}
async save(req, res) {
await Ciclo.create(req.body);
res.redirect('/ciclo-index');
}
}
GraphQL Resolvers
// app/src/api-graphql.js
const resolvers = {
Query: {
healthcheck: () => ({ status: 'OK' }),
systemInformation: () => ({ version: '1.0.0' })
},
Mutation: {
sessionLogin: async (parent, { input }, context) => {
const session = await UsuarioService.login(input);
return session;
}
}
};
3. Camada de Serviços
// app/src/services/services.js
class UsuarioService {
static async login(email, senha) {
// Valida credenciais
// Cria sessão
// Retorna token
}
}
4. Camada de Modelos
Sequelize Models (ORM)
// app/models/usuario.js
module.exports = (sequelize, DataTypes) => {
const Usuario = sequelize.define('Usuario', {
nome: DataTypes.STRING,
email: DataTypes.STRING,
perfis: DataTypes.ARRAY(DataTypes.STRING)
});
Usuario.associate = (models) => {
Usuario.hasMany(models.Oferta);
Usuario.hasMany(models.PedidoConsumidores);
};
return Usuario;
};
Business Models
// app/src/model/Ciclo.js
class Ciclo {
static async getAll() {
const sql = cicloSql.getAll();
return await db.query(sql);
}
static async getCicloAtivo() {
// Lógica de negócio
}
}
5. Camada de Dados
// app/src/db/cicloSql.js
const cicloSql = {
getAll: () => `
SELECT c.*,
COUNT(DISTINCT ce.id) as num_entregas,
COUNT(DISTINCT cc.id) as num_cestas
FROM ciclo c
LEFT JOIN cicloentregas ce ON c.id = ce.cicloid
LEFT JOIN ciclocestas cc ON c.id = cc.cicloid
GROUP BY c.id
ORDER BY c.id DESC
`
};
Frontend
1. Roteamento
// frontend/src/App.tsx
<Routes>
<Route path="/" element={<Index />} />
<Route path="/login" element={<Login />} />
<Route path="/dashboard" element={<Dashboard />} />
{/* Admin */}
<Route path="/admin/login" element={<AdminLogin />} />
<Route path="/admin/dashboard" element={<AdminDashboard />} />
<Route path="/admin/mercados" element={<AdminMercados />} />
{/* Fornecedor */}
<Route path="/fornecedor/login" element={<FornecedorLogin />} />
<Route path="/fornecedor/loja" element={<LojaProdutor />} />
</Routes>
2. Data Fetching (GraphQL)
// frontend/src/hooks/graphql.ts
import { useMutation, useQuery } from '@tanstack/react-query';
import { request, gql } from 'graphql-request';
const LOGIN_MUTATION = gql`
mutation SessionLogin($input: LoginInput!) {
sessionLogin(input: $input) {
usuarioId
token
perfis
}
}
`;
export const useLoginUsuario = () => {
return useMutation({
mutationFn: (input: LoginInput) =>
request(GRAPHQL_ENDPOINT, LOGIN_MUTATION, { input })
});
};
3. State Management
Context API
// frontend/src/contexts/ConsumerContext.tsx
const ConsumerContext = createContext();
export const ConsumerProvider = ({ children }) => {
const [consumer, setConsumer] = useState(null);
const [cart, setCart] = useState([]);
return (
<ConsumerContext.Provider value={{ consumer, cart }}>
{children}
</ConsumerContext.Provider>
);
};
React Query (Cache de servidor)
const { data, isLoading } = useQuery({
queryKey: ['ciclos'],
queryFn: fetchCiclos
});
4. Componentes UI
// frontend/src/components/ui/button.tsx (shadcn/ui)
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md",
{
variants: {
variant: {
default: "bg-primary text-white",
outline: "border border-input",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 px-3",
},
},
}
);
export const Button = ({ variant, size, ...props }) => {
return <button className={buttonVariants({ variant, size })} {...props} />;
};
Modelo de Dados
Entidades Principais
Gestão de Usuários
- Usuario: Usuários do sistema (admin, fornecedor, consumidor)
- Session: Sessões de autenticação (tokens)
Gestão de Ciclos
- Ciclo: Períodos de comercialização
- CicloEntregas: Datas de entrega por ciclo
- CicloCestas: Cestas disponíveis no ciclo
- CicloProdutos: Produtos disponíveis no ciclo
Produtos e Ofertas
- Produto: Catálogo de produtos
- CategoriaProdutos: Categorias dos produtos
- Oferta: Ofertas de fornecedores por ciclo
- OfertaProdutos: Produtos específicos em cada oferta
Cestas e Composição
- Cesta: Tipos de cestas (Kitandinha, PNAE, etc.)
- Composicoes: Templates de composição
- ComposicaoCestaProdutos: Produtos que compõem uma cesta
- ComposicaoCestaOpcoes: Opções alternativas nas cestas
- ComposicaoOfertaProdutos: Link entre composições e ofertas
Pedidos
- PedidoConsumidores: Pedidos de consumidores
- PedidoConsumidoresProdutos: Itens em cada pedido
- PedidosFornecedores: Pedidos consolidados para fornecedores
Logística
- PontoEntrega: Locais de entrega
Estoque
- Movimentacao: Movimentações de estoque
- TipoMovimentacao: Tipos de movimentação
Relacionamentos Principais
Usuario
├─ hasMany → Oferta
└─ hasMany → PedidoConsumidores
Ciclo
├─ hasMany → CicloEntregas
├─ hasMany → CicloCestas
├─ hasMany → CicloProdutos
├─ hasMany → Oferta
└─ hasMany → PedidoConsumidores
Produto
├─ belongsTo → CategoriaProdutos
├─ hasMany → OfertaProdutos
└─ hasMany → Movimentacao
Oferta
├─ belongsTo → Ciclo
├─ belongsTo → Usuario
└─ hasMany → OfertaProdutos
Cesta
├─ hasMany → ComposicaoCestaProdutos
└─ hasMany → ComposicaoCestaOpcoes
PedidoConsumidores
├─ belongsTo → Ciclo
├─ belongsTo → Usuario
└─ hasMany → PedidoConsumidoresProdutos
APIs
GraphQL API
Endpoint: /graphql
Schema:
type ActiveSession {
usuarioId: ID!
token: String!
perfis: [String!]!
}
type HealthCheck {
status: String!
}
input LoginInput {
email: String!
senha: String!
}
type Mutation {
sessionLogin(input: LoginInput!): ActiveSession!
sessionLogout: LogoutResponse!
}
type Query {
healthcheck: HealthCheck!
systemInformation: SystemInformation!
}
Autenticação:
Authorization: Bearer <token>
REST API
Rotas Principais:
GET/POST /ciclo
GET/POST /cesta
GET/POST /produto
GET/POST /oferta
GET/POST /composicao
GET/POST /pedidoconsumidores
GET/POST /usuario
GET /relatorios/*
Autenticação
OAuth 2.0 / OpenID Connect
Fluxo:
1. Usuário acessa aplicação
2. Middleware verifica req.oidc.user
3. Se não autenticado → Redirect para OAuth provider
4. Login bem-sucedido → Callback com token
5. Sistema cria/atualiza usuário no banco
6. Sessão estabelecida
Configuração:
auth({
authRequired: false,
auth0Logout: true,
issuerBaseURL: process.env.issuerBaseURL,
baseURL: process.env.BASE_URL_APP,
clientID: process.env.clientID,
secret: process.env.secret
})
GraphQL Session Management
Autenticação por Token:
// 1. Login
const { sessionLogin } = useMutation();
const result = await sessionLogin({ email, senha });
// result.token armazenado no localStorage
// 2. Requisições autenticadas
Authorization: Bearer <token>
Perfis de Usuário:
master- Super administradoradmin- Administradorfornecedor- Fornecedor/Produtorconsumidor- Consumidorinfo- Apenas visualização
Testing
BDD com Cucumber
Feature Files: /app/features/*.feature
# features/sessions.feature
Feature: Gerenciamento de Sessões
Scenario: Login com sucesso
Given um usuário cadastrado com email "user@example.com"
When eu faço login com email "user@example.com" e senha "123456"
Then o login deve ser bem-sucedido
And eu devo receber um token de sessão
Step Definitions: /app/features/step_definitions/
Testes Unitários
Jest: Configurado para testes de serviços e models
Mocha: E2E tests
Deployment
Docker Compose
Services:
services:
app.dev:
build: ./app
ports:
- "13000:5000"
environment:
DATABASE_URL: postgresql://...
depends_on:
- db.dev
db.dev:
image: postgres:13
environment:
POSTGRES_DB: divinoalimento
volumes:
- pgdata:/var/lib/postgresql/data
frontend:
build: ./frontend
ports:
- "8080:8080"
mock-oauth2-server:
# Para desenvolvimento
ports:
- "8080:8080"
Rake Tasks
# Construir ambiente
rake vivo:constroi
# Iniciar serviços
rake vivo:liga
# Parar serviços
rake vivo:para
# Popular banco de dados
rake vivo:popular
# Rodar migrations
rake vivo:migracao
# Acessar shell do app
rake vivo:sh
# Acessar PostgreSQL
rake vivo:psql
# Ver logs
rake vivo:mensagens
Fluxos de Requisição
Fluxo REST (Legado)
1. Browser → POST /ciclo (form)
2. Express Routes → CicloController.save()
3. Controller → Ciclo.create(data)
4. Model → cicloSql.insert()
5. Sequelize → PostgreSQL INSERT
6. Response → Redirect /ciclo-index
7. Controller → Ciclo.getAll()
8. Model → cicloSql.getAll()
9. Sequelize → PostgreSQL SELECT
10. Controller → res.render('ciclo-index.ejs', { ciclos })
11. EJS → HTML
12. Browser ← HTML response
Fluxo GraphQL (Moderno)
1. React App → useLoginUsuario mutation
2. graphql-request → POST /graphql
3. GraphQL Middleware → Extract token from Authorization header
4. Resolver → sessionLogin(email, senha)
5. UsuarioService.login() → Validate credentials
6. Session.create() → Sequelize INSERT
7. GraphQL Response → { token, usuarioId, perfis }
8. React Query → Cache response
9. React Component → Update state
10. Navigate → /dashboard
Performance e Escalabilidade
Otimizações
-
Backend:
- SQL queries otimizadas com JOINs
- Sequelize connection pooling
- EJS template caching (produção)
-
Frontend:
- Vite hot module replacement
- Code splitting com React Router
- React Query caching
- Lazy loading de componentes
-
Database:
- Índices em migrations
- Connection pooling
- Read replicas (futuro)
Escalabilidade
- Horizontal: Múltiplas instâncias app.dev
- Load Balancer: Nginx/HAProxy
- CDN: Assets estáticos
- Cache: Redis para sessões (futuro)
Próximos Passos
- Conceitos Principais - Ciclos, Mercados, Ofertas
- Referência API GraphQL
- Referência API REST
- Guia de Desenvolvimento
Dúvidas? Consulte o Troubleshooting ou Como Contribuir.