Arquitetura do Sistema
Esta página descreve a arquitetura técnica da plataforma Divino Cestas (commit 2b1881fafe).
🏗️ Visão Geral
A plataforma Divino Alimento é uma aplicação web full-stack construída com tecnologias modernas e arquitetura em camadas.
┌─────────────────────────────────────────────────────┐
│ Cliente (Browser) │
│ Bootstrap 5 + jQuery + EJS │
└───────────────────────┬─────────────────────────────┘
│ HTTP/HTTPS
┌───────────────────────▼─────────────────────────────┐
│ Express.js Server (Node.js) │
│ ┌─────────────────────────────────────────────┐ │
│ │ Controllers (Handlers) │ │
│ └──────────────────┬──────────────────────────┘ │
│ ┌──────────────────▼──────────────────────────┐ │
│ │ Business Logic (Model Layer) │ │
│ └──────────────────┬──────────────────────────┘ │
│ ┌──────────────────▼──────────────────────────┐ │
│ │ Database Layer (SQL Queries) │ │
│ └──────────────────┬──────────────────────────┘ │
└─────────────────────┼─────────────────────────────┘
┌─────────────────────▼─────────────────────────────┐
│ Sequelize ORM + PostgreSQL │
└─────────────────────────────────────────────────────┘
📦 Stack Tecnológico
Backend
- Runtime: Node.js
- Framework: Express.js v4.17.1
- ORM: Sequelize v6.37.1
- Banco de Dados: PostgreSQL (produção), SQLite (testes)
- Template Engine: EJS v3.1.6
- Autenticação: OpenID Connect (express-openid-connect v2.4.0)
Frontend
- UI Framework: Bootstrap 5.0.2
- JavaScript: jQuery 3.6.0
- CSS: Custom CSS + Bootstrap
Ferramentas e Utilitários
- PDF Generation: PDFKit v0.12.1
- CSV Export: json2csv v5.0.7
- Environment Config: dotenv v9.0.1
- Development: Nodemon v2.0.7
- Testing: Mocha v11.7.1, Chai v4.3.6
DevOps
- Containerização: Docker & Docker Compose v2
- Automação: Rake (Ruby-based task runner)
- Migrations: Sequelize CLI v6.6.2
📂 Estrutura do Projeto
DivinoAlimento/
├── app/
│ ├── src/
│ │ ├── controllers/ # 🎮 Controladores (request handlers)
│ │ │ ├── IndexController.js
│ │ │ ├── CicloController.js
│ │ │ ├── CestaController.js
│ │ │ ├── ProdutoController.js
│ │ │ ├── OfertaController.js
│ │ │ ├── ComposicaoController.js
│ │ │ ├── PedidoConsumidoresController.js
│ │ │ ├── UsuarioController.js
│ │ │ ├── PontoEntregaController.js
│ │ │ ├── CategoriaController.js
│ │ │ ├── MovimentacaoController.js
│ │ │ ├── RelatoriosController.js
│ │ │ └── ...
│ │ │
│ │ ├── model/ # 🧠 Lógica de Negócio
│ │ │ ├── Ciclo.js
│ │ │ ├── Cesta.js
│ │ │ ├── Produto.js
│ │ │ ├── Oferta.js
│ │ │ ├── Composicao.js
│ │ │ ├── PedidoConsumidores.js
│ │ │ ├── Usuario.js
│ │ │ └── ...
│ │ │
│ │ ├── db/ # 💾 Camada de Banco de Dados
│ │ │ ├── cicloSql.js
│ │ │ ├── cestaSql.js
│ │ │ ├── produtoSql.js
│ │ │ ├── ofertaSql.js
│ │ │ ├── composicaoSql.js
│ │ │ ├── usuarioSql.js
│ │ │ └── ...
│ │ │
│ │ ├── views/ # 🎨 Templates EJS
│ │ │ ├── index.ejs
│ │ │ ├── ciclo.ejs
│ │ │ ├── cesta.ejs
│ │ │ ├── oferta.ejs
│ │ │ ├── parts/
│ │ │ │ ├── header.ejs
│ │ │ │ └── page-header.ejs
│ │ │ └── ...
│ │ │
│ │ ├── server.js # 🚀 Servidor Express
│ │ └── routes.js # 🛣️ Definição de Rotas
│ │
│ ├── models/ # 📊 Modelos Sequelize ORM
│ │ ├── usuario.js
│ │ ├── ciclo.js
│ │ ├── cesta.js
│ │ ├── produto.js
│ │ ├── oferta.js
│ │ └── ...
│ │
│ ├── migrations/ # 🔄 Migrations do Banco
│ │ ├── 20210501000000-create-usuario.js
│ │ ├── 20210502000000-create-ciclo.js
│ │ └── ...
│ │
│ ├── config/ # ⚙️ Configurações
│ │ └── config.js
│ │
│ ├── public/ # 🌐 Assets Estáticos
│ │ ├── images/
│ │ └── styles/
│ │
│ └── package.json
│
├── env.example # 🔐 Template de Variáveis
├── Rakefile # 🛠️ Tarefas de Automação
├── compose.live.yml # 🐳 Docker Compose
└── README.md
🔀 Arquitetura em Camadas
1️⃣ Camada de Apresentação (Views)
Responsabilidade: Renderização de páginas HTML para o usuário
- Templates EJS com Bootstrap 5
- Formulários dinâmicos com validação client-side
- JavaScript/jQuery para interatividade
Arquivos: app/src/views/*.ejs
2️⃣ Camada de Controle (Controllers)
Responsabilidade: Processar requisições HTTP e coordenar fluxo
// Exemplo: CicloController.js
class CicloController {
async showIndex(req, res) {
// 1. Recebe requisição
// 2. Chama camada de negócio
// 3. Retorna resposta/view
}
async save(req, res) {
// Processa POST, valida, salva
}
}
Arquivos: app/src/controllers/*Controller.js
3️⃣ Camada de Negócio (Model)
Responsabilidade: Lógica de negócio e regras
// Exemplo: Ciclo.js (model)
class Ciclo {
static async getAll() { /* ... */ }
static async findOrCreate(data) { /* ... */ }
static async getCicloAtivo() { /* ... */ }
static validarDatas(dataInicio, dataFim) { /* ... */ }
}
Arquivos: app/src/model/*.js
4️⃣ Camada de Dados (Database Layer)
Responsabilidade: Queries SQL complexas
// Exemplo: cicloSql.js
const cicloSql = {
getAll: () => `
SELECT c.*,
COUNT(DISTINCT ce.id) as num_entregas
FROM ciclo c
LEFT JOIN cicloentregas ce ON c.id = ce.cicloid
GROUP BY c.id
ORDER BY c.id DESC
`,
getById: (id) => `
SELECT c.*, ce.*, cc.*
FROM ciclo c
LEFT JOIN cicloentregas ce ON c.id = ce.cicloid
LEFT JOIN ciclocestas cc ON c.id = cc.cicloid
WHERE c.id = ${id}
`
};
Arquivos: app/src/db/*Sql.js
5️⃣ Camada de Persistência (ORM)
Responsabilidade: Mapeamento objeto-relacional
// Exemplo: models/ciclo.js
module.exports = (sequelize, DataTypes) => {
const Ciclo = sequelize.define('Ciclo', {
nome: DataTypes.STRING,
dataInicio: DataTypes.DATE,
dataFim: DataTypes.DATE,
ativo: DataTypes.BOOLEAN
});
Ciclo.associate = (models) => {
Ciclo.hasMany(models.CicloEntregas);
Ciclo.hasMany(models.CicloCestas);
};
return Ciclo;
};
Arquivos: app/models/*.js
🔐 Autenticação e Segurança
OpenID Connect (OAuth 2.0)
// server.js
const { auth } = require('express-openid-connect');
app.use(auth({
authRequired: false,
auth0Logout: true,
issuerBaseURL: process.env.issuerBaseURL,
baseURL: process.env.BASE_URL_APP,
clientID: process.env.clientID,
secret: process.env.secret
}));
Fluxo de Autenticação
1. Usuário acessa aplicação
↓
2. Middleware verifica req.oidc.user
↓
3. Se não autenticado → Redireciona para login OAuth
↓
4. Usuário faz login (Auth0 ou Mock Server)
↓
5. Callback retorna com token
↓
6. Sistema busca/cria usuário no banco
↓
7. Sessão estabelecida (req.oidc.user disponível)
Perfis de Usuário
perfil: DataTypes.ARRAY(DataTypes.ENUM(
'info', // Apenas visualização
'master', // Super administrador
'admin', // Administrador
'fornecedor', // Fornecedor/Agricultor
'consumidor' // Consumidor
))
🗄️ Modelo de Dados
Diagrama de Relacionamentos (Simplificado)
┌─────────────┐
│ Usuario │────┐
└─────────────┘ │
│ hasMany
┌─────────────┐ ▼
│ Ciclo │ ┌───────┐ ┌─────────────────────┐
│ │─▶│Oferta │────▶│ OfertaProdutos │
│ │ └───────┘ └─────────────────────┘
│ │
│ │ ┌──────────────┐
│ │─▶│CicloEntregas │
│ │ └──────────────┘
│ │
│ │ ┌─────────────┐ ┌──────────────┐
│ │─▶│ CicloCestas │────▶│ Composicoes │
│ │ └─────────────┘ └──────────────┘
│ │ │ │
│ │ │ ▼
│ │ │ ┌─────────────────────┐
│ │ │ │ComposicaoOfertaProd │
│ │ │ └─────────────────────┘
│ │ │
│ │ ▼
│ │ ┌───────────┐
│ │ │ Cesta │
└─────────────┘ └───────────┘
│
│ hasMany
▼
┌──────────────────────┐
│ PedidoConsumidores │
└──────────────────────┘
│
│ hasMany
▼
┌──────────────────────────────┐
│PedidoConsumidoresProdutos │
└──────────────────────────────┘
┌─────────────┐
│ Produto │
└─────────────┘
│
│ belongsTo
▼
┌──────────────────┐
│CategoriaProdutos │
└──────────────────┘
🚀 Fluxo de Requisição
Exemplo: Criar um Pedido de Consumidor
1. Browser → GET /pedidoconsumidores/:id
↓
2. routes.js → PedidoConsumidoresController.showIndex()
↓
3. Controller → PedidoConsumidores.getByCicloAndUser(cicloId, userId)
↓
4. Model → pedidoConsumidoresSql.getById(id)
↓
5. Database Layer → executa SQL complexo com JOINs
↓
6. PostgreSQL retorna dados
↓
7. Model processa e retorna objeto
↓
8. Controller renderiza view: res.render('pedidoConsumidores.ejs', { data })
↓
9. EJS + Bootstrap → HTML final
↓
10. Browser exibe página
Exemplo: Salvar Pedido (POST)
1. Browser → POST /pedidoconsumidores (form data)
↓
2. Controller valida dados
↓
3. Model.save() → Transaction no Sequelize
↓
4. Salva PedidoConsumidores
↓
5. Salva PedidoConsumidoresProdutos (items)
↓
6. Commit da transaction
↓
7. Redireciona → /pedidoconsumidoresconfirmacao/:id
🐳 Infraestrutura Docker
Services
# compose.live.yml
services:
app.dev:
build: ./app
ports:
- "${APP_PORT}:3000"
depends_on:
- db.dev
environment:
- DATABASE_URL=postgresql://...
db.dev:
image: postgres:13
environment:
- POSTGRES_DB=divinoalimento
- POSTGRES_USER=divino
- POSTGRES_PASSWORD=***
volumes:
- pgdata:/var/lib/postgresql/data
Rake Tasks
# Rakefile
namespace :vivo do
task :constroi # docker-compose build
task :liga # docker-compose up -d
task :para # docker-compose down
task :popular # seed database
task :psql # access database
task :sh # access app container
end
📊 Performance e Escalabilidade
Otimizações Implementadas
- Queries SQL Otimizadas: JOINs complexos na camada de dados
- Índices no Banco: Migrations incluem índices estratégicos
- Connection Pooling: Sequelize gerencia pool de conexões
- Static Assets: Servidos via Express static middleware
- EJS Caching: Templates compilados em produção
Considerações para Escala
- Horizontal Scaling: Adicionar mais instâncias app.dev
- Load Balancer: Nginx/HAProxy na frente
- Database Read Replicas: Para queries pesadas de relatórios
- CDN: Para assets estáticos
- Redis: Para sessões distribuídas (futuro)
🧪 Ambiente de Testes
// config/config.js
test: {
dialect: 'sqlite',
storage: ':memory:',
logging: false
}
- Banco em Memória: SQLite para testes rápidos
- Isolamento: Cada teste roda em transação
- Mocha + Chai: Framework de testes
📝 Referências de Arquivos
| Componente | Localização |
|---|---|
| Servidor | app/src/server.js:1 |
| Rotas | app/src/routes.js:1 |
| Controllers | app/src/controllers/ |
| Models | app/src/model/ |
| Database Layer | app/src/db/ |
| ORM Models | app/models/ |
| Views | app/src/views/ |
| Config | app/config/config.js:1 |
| Migrations | app/migrations/ |
🔗 Próximos Passos
- Conceitos Principais - Entenda Ciclos, Cestas, Ofertas
- Referência de API - Documentação completa das rotas
- Modelos de Dados - Estrutura detalhada do banco
Dúvidas sobre arquitetura? Consulte o Troubleshooting ou abra uma issue no GitHub.