Visão Geral da Arquitetura
Ouroboros opera dentro de um ambiente controlado: a rede local de um evento escolar. Esse contexto define tudo. Diferente de uma aplicação web convencional, não há usuários anônimos, não há escalabilidade horizontal necessária, e o maior risco não é carga — é indisponibilidade de rede.
A arquitetura reflete isso.
Histórico e Dimensionamento (Base 2025)
Os dados da edição de 2025 da Feira da Troca confirmam que a carga é perfeitamente suportável por uma única instância local:
- Volume Total: ~6.200 produtos cadastrados.
- Participantes Ativos: 257 comandas (cartões) entregues.
- Transações: 2.320 produtos trocados ao longo do evento.
- Pico de Carga: ~450 transações em 30 minutos (menos de 1 transação a cada 4 segundos no pico).
Com esses números, qualquer notebook moderno processando SQLite consegue manter latências abaixo de 10ms sem esforço, tornando qualquer complexidade de nuvem ou escalabilidade horizontal desnecessária e contraproducente.
Componentes
Servidor Principal
O núcleo do sistema. Roda em qualquer máquina na rede local (um notebook comum é suficiente).
Responsabilidades:
- Processar e persistir todas as transações
- Manter o event store imutável (SQLite)
- Transmitir eventos em tempo real via WebSocket
- Sincronizar eventos confirmados pro Firebase de forma assíncrona
Stack: FastAPI (Python) + SQLite + Uvicorn
Terminais de Loja
Qualquer dispositivo com browser conectado à rede local. Sem instalação.
Responsabilidades:
- Inserir o ID da comanda do cliente
- Enviar requisição de débito ao servidor
- Receber confirmação em tempo real
- Exibir histórico de transações da sessão
Stack: React + Vite (interface de demonstração — pode ser substituída)
Interface de Administração e Operação (Admin & Banco)
Interface de gestão para organizadores e sub-administradores (operadores do Banco).
Responsabilidades:
- Admin Principal: Controle total, gestão de lojas (criar/revogar tokens), visão macro da economia.
- Banco (Operador): Cadastro de novos produtos/categorias, emissão de comandas com saldo inicial (carrinho de avaliação).
- Visualização do estado geral da economia.
Sobre o frontend
O frontend React incluído é uma interface de demonstração básica. Implementa todos os fluxos (login, emissão de comandas, carrinho de vendas, débito, gestão de lojas) mas foi construído com foco em funcionalidade, não em design final. A interface pode ser livremente redesenhada, customizada ou substituída por qualquer tecnologia frontend — o backend (API REST + WebSocket) é a camada estável.
Firebase Firestore (camada de leitura)
Espelho eventual dos eventos confirmados. Nunca é consultado pelo servidor principal durante operações críticas.
Responsabilidades:
- Receber eventos síncronizados do servidor (quando há internet)
- Servir consultas de saldo para o cliente final (celular)
- Não bloquear nunca — falha silenciosa, retry automático
Diagrama de componentes
graph TB
subgraph rede_local["Rede Local do Evento"]
terminal_a["Terminal Loja A\n(browser)"]
terminal_b["Terminal Loja B\n(browser)"]
admin["Painel Admin\n(browser)"]
servidor["Servidor Principal\nFastAPI + Uvicorn"]
sqlite[("SQLite\nEvent Store")]
static["Assets Estáticos\n(React build)"]
terminal_a -- "WS (Lojas)" --> servidor
terminal_b -- "WS (Lojas)" --> servidor
admin -- "WS (Operacional)" --> servidor
admin -- "REST (Relatórios)" --> servidor
servidor -- "leitura/escrita" --> sqlite
servidor -- "serve" --> static
end
subgraph nuvem["Nuvem (opcional)"]
firebase[("Firebase\nFirestore")]
end
subgraph cliente["Cliente (celular)"]
app["Browser\n(consulta saldo)"]
end
servidor -- "sync assíncrono\nbest-effort" --> firebase
firebase -- "leitura" --> app
Gestão de Produtos e Categorias
O Banco é responsável pela entrada de novos produtos no ecossistema da feira. A arquitetura de produtos segue uma estratégia de Categorização Generalizada por motivos de eficiência operacional (evitar o "trabalho braçal" de etiquetar milhares de itens individualmente):
- Abstração de Itens: Em vez de IDs únicos por objeto físico, o sistema trabalha com categorias (ex: CAMISETA, JAQUETA, BOLSA, BRINQUEDO).
- Finalidade Organizacional: A categorização serve para fins estatísticos e de auditoria, sem travar a venda em caso de erro humano (ex: vender uma Jaqueta como Camiseta).
- Controle de Estoque: O sistema rastreia o volume total de entradas e saídas por categoria de forma cumulativa (incremento), permitindo comparar o que entrou no Banco vs o que circulou nas lojas.
- Preços Fixos: Cada categoria possui uma tabela de preços definida centralmente no Banco.
Modelo de dados
O sistema opera com cinco entidades centrais:
Comanda
id: UUID (interno)
code: string (ID curto: "F001" a "F999")
holder_name: string
created_at: timestamp
Event
id: UUID
type: ENUM (credit | debit)
comanda_id: UUID → Comanda
amount: integer (centavos fictícios)
store_id: UUID → Store
timestamp: timestamp
synced_to_firebase: boolean
Store
id: UUID
name: string
theme: string
terminal_token: string
Category (Tabela de Preços e Categorização)
id: UUID
name: string (ex: "Jaqueta")
price: integer (centavos fictícios)
total_entries: integer (contador acumulativo de entrada no banco)
total_exits: integer (contador acumulativo de vendas confirmadas)
Balance (view derivada — nunca armazenada diretamente)
comanda_id: UUID
balance: SUM(credits) - SUM(debits)
Por que saldo é uma view?
O saldo nunca é um campo armazenado. Ele é sempre computado ao agregar o event store. Isso garante que não existe estado inconsistente: se o event log está correto, o saldo está correto. Ver ADR-003 para a justificativa completa.
Fluxo de uma transação (visão geral)
- Cliente informa o número/ID da comanda no terminal da loja
- Operador digita o ID e envia
debit_requestvia WebSocket - Servidor valida saldo disponível consultando o event store
- Se válido: persiste o evento no SQLite, retorna
debit_confirmed - Todos os outros terminais conectados recebem broadcast do evento
- Worker assíncrono enfileira o evento para sync com Firebase
Tempo de resposta esperado: < 50ms na rede local (SQLite + loopback)
Decisões de design
As principais escolhas arquiteturais estão documentadas como ADRs (Architecture Decision Records). O projeto foca em suportar o fluxo de usuários (Lojas, Clientes) e os níveis de permissão administrativa (Admin e Banco/Sub-admin).
| Decisão | Documento |
|---|---|
| Por que a operação principal é local, não cloud | ADR-001: Local-First |
| Por que SQLite em vez de PostgreSQL ou outro banco | ADR-002: SQLite |
| Por que Event Sourcing em vez de CRUD convencional | ADR-003: Event Sourcing |