Uma aplicação de estudo de e-commerce desenvolvida em Rust com Actix Web, PostgreSQL, MongoDB e Elasticsearch. Este projeto demonstra a implementação de uma arquitetura robusta para sistemas de comércio eletrônico, incluindo gestão de produtos, usuários, tenants e autenticação.
Crie um arquivo .env na raiz do projeto com as seguintes variáveis:
# Configurações do Servidor
SERVER_HOST=127.0.0.1
SERVER_PORT=8080
# Ambiente da aplicação
APP_ENVIRONMENT=development
# Configurações do Banco de Dados PostgreSQL
DATABASE_URL=postgresql://username:password@localhost:5432/database_name
DATABASE_MAX_CONNECTIONS=5
# Configurações do MongoDB
MONGO_URI=mongodb://localhost:27017
# Configurações do Elasticsearch
ELASTICSEARCH_URL=http://localhost:9200
ELASTICSEARCH_INDEX_PREFIX=app
# Configurações JWT
JWT_SECRET=your_super_secret_jwt_key_that_is_at_least_32_characters_long
JWT_EXPIRES_IN=86400Certifique-se de ter os seguintes serviços rodando:
- PostgreSQL: Banco de dados principal
- MongoDB: Banco de dados NoSQL
- Elasticsearch: Motor de busca
# Verificar se compila
cargo check
# Executar em modo desenvolvimento
cargo run
# Executar em modo release
cargo run --releaseO servidor estará disponível em http://127.0.0.1:8080
Use o script start_new_app.sh para criar rapidamente um novo módulo de aplicação:
# Criar um app com migration
./start_new_app.sh User
# Criar um app sem migration
./start_new_app.sh Product --no_migrateO script criará automaticamente:
- 📁
src/apps/user/(diretório do app) - 📄
mod.rs(declarações de módulos) - 📄
models.rs(modelos de dados) - 📄
routes.rs(rotas da API) - 📄
services.rs(lógica de negócio) - 📄
repositories.rs(acesso ao banco) - 📄
tests.rs(testes unitários) - 🗄️
migrations/YYYYMMDDHHMMSS_create_users.sql(migration do banco)
Se precisar remover um app criado, use o script revert_new_app.sh:
./revert_new_app.sh UserO script irá:
- ❌ Remover o diretório do app
- ❌ Remover as migrations relacionadas
- ❌ Remover a declaração do módulo
⚠️ Pedir confirmação antes de executar
O script agora gera código com uma arquitetura robusta de tratamento de erros em três camadas:
Antes:
pub async fn list_items(app_state: web::Data<AppState>) -> impl Responder {
HttpResponse::Ok().json(serde_json::json!({
"message": "Lista de Item",
"data": []
}))
}Depois:
pub async fn list_items(app_state: web::Data<AppState>) -> Result<impl Responder, AppError> {
Ok(HttpResponse::Ok().json(serde_json::json!({
"message": "Lista de Item",
"data": []
})))
}Benefícios:
- ✅ Tratamento de erros automático: Actix Web converte
AppErrorem respostas HTTP apropriadas - ✅ Consistência: Todas as rotas seguem o mesmo padrão de retorno
- ✅ Debugging melhorado: Erros são rastreados até a origem
- ✅ Respostas padronizadas: Erros seguem o formato definido em
AppError
Antes:
pub async fn list_users(app_state: &AppState) -> Result<Vec<User>, Box<dyn std::error::Error>> {
let repository = UserRepository::new(app_state);
repository.find_all().await
}Depois:
pub async fn list_items(app_state: &AppState) -> Result<PaginatedResponse<Item>, AppError> {
let repository = ItemRepository::new(app_state);
let items = repository.find_all().await
.map_err(|e| AppError::database_error(e.to_string()))?;
Ok(PaginatedResponse {
count: items.len() as i64,
results: items,
limit: 10,
offset: 0,
})
}Benefícios:
- ✅ Paginação nativa: Todas as listagens retornam dados paginados
- ✅ Metadados úteis: Inclui
count,limit,offsetpara frontend - ✅ Padrão consistente: Formato uniforme para todas as APIs de listagem
- ✅ Melhor UX: Frontend pode implementar paginação facilmente
- ✅ Conversão de erros: Erros de banco são convertidos para
AppError
Antes:
pub async fn find_all(&self) -> Result<Vec<User>, Box<dyn std::error::Error>> {
// código...
}Depois:
pub async fn find_all(&self) -> Result<Vec<User>, sqlx::Error> {
// código...
}Benefícios:
- ✅ Tipagem específica: Erro específico do SQLx em vez de erro genérico
- ✅ Melhor performance: Evita boxing de erros desnecessário
- ✅ Debugging preciso: Erros de banco são mais específicos e informativos
- ✅ Consistência com SQLx: Usa o tipo de erro nativo da biblioteca
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Repository │ │ Service │ │ Route │ │ HTTP Client │
│ │ │ │ │ │ │ │
│ sqlx::Error │───▶│ AppError │───▶│ Result<impl │───▶│ HTTP Response │
│ │ │ │ │ Responder, │ │ (com status │
│ │ │ │ │ AppError> │ │ apropriado) │
└─────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │ │
│ map_err() │ automatic conversion │ actix-web handles │
▼ ▼ ▼ ▼
Database errors Business logic errors Route errors Client response
pub enum AppError {
Conflict(Option<String>),
DatabaseError(Option<String>),
NotFound(Option<String>),
Unauthorized(Option<String>),
BadRequest(Option<String>),
InternalError(Option<String>),
}pub struct PaginatedResponse<T> {
pub count: i64, // Total de registros
pub results: Vec<T>, // Dados da página atual
pub limit: i64, // Limite por página
pub offset: i64, // Offset da página
}-
🔒 Tratamento de Erros Robusto
- Cada camada tem responsabilidade específica
- Erros são propagados de forma controlada
- Respostas HTTP consistentes
-
📊 Paginação Padrão
- Todas as listagens são paginadas automaticamente
- Metadados úteis para frontend
- Performance otimizada para grandes datasets
-
⚡ Performance Melhorada
- Menos uso de
Box<dyn Error>genérico - Tipagem específica para erros de banco
- Conversões eficientes entre tipos
- Menos uso de
-
🛠️ Manutenibilidade
- Código mais previsível e fácil de manter
- Padrões consistentes em toda a aplicação
- Debugging simplificado
-
🎯 Experiência do Desenvolvedor
- Script gera código pronto para produção
- Menos boilerplate para implementar
- Estrutura escalável e profissional
# Criar um novo app com a nova arquitetura
./start_new_app.sh Product
# O script gera automaticamente:
# - Routes com Result<impl Responder, AppError>
# - Services com Result<PaginatedResponse<T>, AppError>
# - Repositories com Result<T, sqlx::Error>
# - Tratamento de erros em todas as camadas
# - Paginação automática para listagenssrc/
├── app_core/ # Núcleo da aplicação
│ ├── app_state.rs # Estado global da aplicação
│ ├── app_error.rs # Tratamento centralizado de erros
│ ├── databases/ # Configurações de banco de dados
│ ├── app_routes.rs # Definição de rotas
│ ├── settings.rs # Configurações da aplicação
│ └── ...
├── apps/ # Módulos da aplicação
│ ├── user/ # Sistema de usuários e autenticação
│ │ ├── mod.rs
│ │ ├── models.rs
│ │ ├── routes.rs # Result<impl Responder, AppError>
│ │ ├── services.rs # Result<PaginatedResponse<T>, AppError>
│ │ ├── repositories.rs # Result<T, sqlx::Error>
│ │ ├── keycloak/ # Integração com Keycloak
│ │ └── tests.rs
│ ├── product/ # 🛍️ Sistema de produtos (E-commerce)
│ │ ├── mod.rs
│ │ ├── models.rs # Product, CreateProductRequest, UpdateProductRequest
│ │ ├── routes.rs # CRUD completo com tratamento de erros
│ │ ├── services.rs # Lógica de negócio e validações
│ │ ├── repositories.rs # Acesso ao banco PostgreSQL
│ │ └── tests.rs # Testes abrangentes
│ ├── tenant/ # Sistema de multi-tenancy
│ ├── orchestrator/ # Gestão de processos de negócio
│ ├── sync_app/ # Sistema de sincronização
│ └── mod.rs
├── utils/ # Utilitários
│ ├── pagination.rs # PaginatedResponse<T>
│ ├── validation.rs # Validadores customizados
│ ├── jwt.rs # Gestão de tokens JWT
│ └── ...
└── main.rs # Ponto de entrada
- ✅ Servidor Actix Web configurado
- ✅ Conexão com PostgreSQL (banco principal)
- ✅ Conexão com MongoDB (dados NoSQL)
- ✅ Conexão com Elasticsearch (busca e indexação)
- ✅ Sistema de configurações por ambiente
- ✅ Logging com tracing
- ✅ Estrutura modular escalável
- ✅ Scripts de automação para criação de apps
- ✅ Sistema de migrations automático
- ✅ Tratamento robusto de erros com AppError
- ✅ Paginação automática com PaginatedResponse
- ✅ Tipagem específica para erros de banco
- ✅ Arquitetura em camadas com responsabilidades bem definidas
- ✅ Sistema de gestão de tokens para autenticação e recuperação
- ✅ Validadores customizados para email, senha, telefone, CPF e data
- ✅ Sistema de Usuários: Cadastro, login, perfis e autenticação
- ✅ Sistema de Produtos: CRUD completo com gestão de estoque e preços
- ✅ Sistema de Tenants: Multi-tenancy para diferentes lojas
- ✅ Sistema de Orquestradores: Gestão de processos de negócio
- ✅ Sistema de Sincronização: Produtor/consumidor para eventos
O projeto inclui validadores customizados para garantir a qualidade dos dados de entrada.
| Validador | Função | Regras |
|---|---|---|
validate_email |
Regex: ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ |
|
| Password | validate_password |
Mínimo 8 caracteres, letras e números obrigatórios |
| Phone | validate_phone |
Formato internacional: +5511999999999 |
| Document | validate_document |
CPF: 000.000.000-00 |
| Birth Date | validate_birth_date |
Data: YYYY-MM-DD |
use crate::utils::validation::{validate_email, validate_password, validate_phone, validate_document, validate_birth_date};
#[derive(Debug, Serialize, Deserialize, Validate)]
pub struct UserRequest {
#[validate(custom = "validate_email")]
pub email: String,
#[validate(custom = "validate_password")]
pub password: String,
}
#[derive(Debug, Deserialize, Validate, Serialize)]
pub struct ProfileRequest {
#[validate(custom = "validate_phone")]
pub phone: Option<String>,
#[validate(custom = "validate_document")]
pub document: Option<String>,
#[validate(custom = "validate_birth_date")]
pub birth_date: Option<String>,
}- Formato padrão de email
- Aceita caracteres especiais:
.,_,%,+,- - Domínio válido com TLD de pelo menos 2 caracteres
- Mínimo 8 caracteres
- Pelo menos uma letra (maiúscula ou minúscula)
- Pelo menos um número
- Aceita caracteres especiais:
@,$,!,%,*,#,?,&
- Formato internacional
- Opcional:
+no início - 1-15 dígitos numéricos
- Exemplo:
+5511999999999
- Formato CPF brasileiro
- Padrão:
000.000.000-00 - Pontos e hífen obrigatórios
- Formato ISO:
YYYY-MM-DD - Data válida (não aceita datas inexistentes)
- Exemplo:
1990-12-25
- ✅ Validação consistente em toda a aplicação
- ✅ Mensagens de erro personalizadas em português
- ✅ Regras específicas para o contexto brasileiro
- ✅ Reutilização em múltiplos modelos
- ✅ Manutenibilidade centralizada
O módulo de produtos é o coração do sistema de e-commerce, implementando todas as funcionalidades necessárias para gestão de catálogo de produtos.
| Operação | Endpoint | Método | Descrição |
|---|---|---|---|
| Listar Produtos | /api/v1/products/ |
GET |
Lista paginada com filtros (nome, preço, estoque) |
| Buscar Produto | /api/v1/products/{id} |
GET |
Busca produto específico por ID |
| Criar Produto | /api/v1/products/ |
POST |
Cria novo produto com validações |
| Atualizar Produto | /api/v1/products/{id} |
PUT |
Atualiza produto existente |
| Deletar Produto | /api/v1/products/{id} |
DELETE |
Remove produto (soft delete) |
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Product {
pub id: Uuid, // Identificador único
pub tenant_id: Uuid, // ID do tenant/loja
pub name: String, // Nome do produto
pub slug: String, // URL amigável
pub short_description: Option<String>, // Descrição curta
pub description: Option<String>, // Descrição completa
pub price: BigDecimal, // Preço em centavos
pub stock_quantity: i32, // Quantidade em estoque
pub attributes: Option<serde_json::Value>, // Atributos customizados
pub is_active: bool, // Status ativo/inativo
pub dt_created: DateTime<Utc>, // Data de criação
pub dt_updated: DateTime<Utc>, // Data de atualização
pub dt_deleted: Option<DateTime<Utc>>, // Soft delete
}- ✅ Nome: Obrigatório, não pode ser vazio
- ✅ Preço: Deve ser positivo (em centavos)
- ✅ Estoque: Deve ser não-negativo
- ✅ Tenant: Produtos são isolados por loja
- ✅ Slug: Geração automática baseada no nome
O módulo implementa tratamento robusto de erros:
// Service retorna erro 404 quando produto não é encontrado
pub async fn get_product(app_state: &AppState, id: Uuid) -> Result<Product, AppError> {
let repository = ProductRepository::new(app_state);
let product = repository.find_by_id(id).await?;
match product {
Some(product) => Ok(product),
None => Err(AppError::not_found("Produto não encontrado")),
}
}| Status | Operação | Body |
|---|---|---|
| 200 | Listar/Buscar/Atualizar | JSON com dados do produto |
| 201 | Criar | JSON com mensagem de sucesso e dados |
| 204 | Deletar | Sem conteúdo (sucesso) |
| 404 | Produto não encontrado | JSON com mensagem de erro |
| 400 | Dados inválidos | JSON com detalhes da validação |
| 500 | Erro interno | JSON com mensagem de erro |
#[derive(Debug, Deserialize)]
pub struct ProductListParams {
pub name: Option<String>, // Filtrar por nome
pub min_price: Option<i64>, // Preço mínimo
pub max_price: Option<i64>, // Preço máximo
pub limit: Option<i64>, // Limite por página
pub offset: Option<i64>, // Offset para paginação
pub is_active: Option<bool>, // Filtrar por status
}# Listar produtos ativos com preço entre R$ 10 e R$ 100
GET /api/v1/products/?min_price=1000&max_price=10000&is_active=true&limit=20
# Buscar produto específico
GET /api/v1/products/550e8400-e29b-41d4-a716-446655440000
# Criar novo produto
POST /api/v1/products/
{
"name": "Smartphone XYZ",
"short_description": "Smartphone de última geração",
"description": "Smartphone com câmera de 48MP...",
"price": 199900, // R$ 1.999,00
"stock_quantity": 50,
"is_active": true
}O módulo inclui testes abrangentes:
- ✅ Testes de Models: Validação de criação e atualização
- ✅ Testes de Services: Lógica de negócio e tratamento de erros
- ✅ Testes de Validação: Campos obrigatórios e formatos
- ✅ Testes de Integração: Cenários de erro e sucesso
- ✅ Funções Auxiliares: Dados de teste reutilizáveis
O projeto inclui um sistema completo de gestão de tokens para autenticação e recuperação de senha.
CREATE TABLE user_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
code TEXT NOT NULL,
token_type TEXT DEFAULT 'reset_password',
expires_at TIMESTAMP NOT NULL,
consumed BOOLEAN DEFAULT FALSE,
dt_created TIMESTAMP NOT NULL DEFAULT now()
);
CREATE INDEX idx_user_tokens_user_id ON user_tokens(user_id);
CREATE INDEX idx_user_tokens_token_type ON user_tokens(token_type);| Tipo | Descrição | Expiração | Uso |
|---|---|---|---|
confirm_email |
Confirmação de email | 1 hora | Cadastro de usuário |
reset_password |
Reset de senha | 1 hora | Recuperação de senha |
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct UserToken {
pub id: Uuid,
pub user_id: Uuid,
pub code: String,
pub token_type: String,
pub expires_at: DateTime<Utc>,
pub consumed: bool,
pub dt_created: DateTime<Utc>,
}O TokenRepository fornece métodos para gerenciar tokens:
pub struct TokenRepository<'a> {
app_state: &'a AppState,
}
impl<'a> TokenRepository<'a> {
// Criar token para usuário
pub async fn create_token(
&self,
user_id: Uuid,
token_type: &str,
) -> Result<UserToken, sqlx::Error>
// Buscar token válido por user_id e token_type
pub async fn find_valid_token(
&self,
user_id: Uuid,
token_type: &str,
) -> Result<Option<UserToken>, sqlx::Error>
// Buscar token válido por código
pub async fn find_valid_token_by_code(
&self,
code: &str,
token_type: &str,
) -> Result<Option<UserToken>, sqlx::Error>
// Marcar token como consumido
pub async fn mark_as_consumed(&self, token_id: Uuid) -> Result<(), sqlx::Error>
}// 1. Criar usuário e perfil
let user = User::new(...);
let profile = Profile::new(user.id);
// 2. Salvar no banco
repository.create_user_with_profile(&user, &profile).await?;
// 3. Criar token de confirmação
let confirm_token = token_repo
.create_token(user.id, "confirm_email")
.await?;
// 4. Enviar email (implementação futura)
println!("Token de confirmação: {}", confirm_token.code);// 1. Verificar se usuário existe
let user = repository.find_by_email(&email).await?;
// 2. Criar token de reset
let reset_token = token_repo
.create_token(user.id, "reset_password")
.await?;
// 3. Enviar email (implementação futura)
println!("Token de reset: {}", reset_token.code);// 1. Validar token
let token = token_repo
.find_valid_token_by_code(&code, "reset_password")
.await?
.ok_or_else(|| AppError::bad_request("Token inválido"))?;
// 2. Alterar senha
repository.update_password(token.user_id, &hashed_password).await?;
// 3. Marcar token como consumido
token_repo.mark_as_consumed(token.id).await?;// 1. Validar token
let token = token_repo
.find_valid_token_by_code(&code, "confirm_email")
.await?
.ok_or_else(|| AppError::bad_request("Token inválido"))?;
// 2. Confirmar email
profile_repo.confirm_email(token.user_id).await?;
// 3. Marcar token como consumido
token_repo.mark_as_consumed(token.id).await?;| Método | Endpoint | Descrição |
|---|---|---|
POST |
/api/v1/auth/register/ |
Cadastro com token de confirmação |
POST |
/api/v1/auth/login/ |
Login do usuário |
POST |
/api/v1/auth/forgot-password/ |
Solicitar reset de senha |
POST |
/api/v1/auth/change-password/ |
Alterar senha com token |
GET |
/api/v1/auth/confirm-email/{code} |
Confirmar email com token |
- Expiração automática: Tokens expiram em 1 hora
- Uso único: Tokens são marcados como consumidos após uso
- Validação de tipo: Cada token tem um tipo específico
- Limpeza automática: Tokens expirados não são retornados
- Índices otimizados: Busca eficiente por user_id e token_type
- Envio de emails: Integração com serviço de email
- Rate limiting: Limitar tentativas de criação de tokens
- Auditoria: Log de todas as operações com tokens
- Notificações: Webhooks para eventos de token
- Expiração configurável: Diferentes tempos por tipo de token
- Sistema de Categorias: Organização hierárquica de produtos
- Sistema de Imagens: Upload e gestão de imagens de produtos
- Sistema de Variações: Produtos com diferentes opções (cor, tamanho, etc.)
- Sistema de Avaliações: Comentários e ratings dos clientes
- Sistema de Descontos: Cupons e promoções
- Sistema de Carrinho: Gestão de carrinho de compras
- Sistema de Pedidos: Processamento e gestão de pedidos
- Sistema de Pagamentos: Integração com gateways de pagamento
- Configurar paginação dinâmica (limit/offset via query params)
- Implementar cache para melhorar performance
- Adicionar documentação OpenAPI/Swagger
- Implementar busca full-text com Elasticsearch
- Sistema de notificações para eventos de produtos
- Logs de auditoria para todas as operações
- Rate limiting para APIs públicas
- Métricas e monitoramento com Prometheus
- Interface administrativa para gestão de produtos
- API de busca com filtros avançados
- Sistema de tags para categorização
- Exportação de dados (CSV, JSON)
- Importação em lote de produtos
- Dashboard de métricas de vendas