Architecture (C4)
O modelo C4 descreve software em quatro níveis de zoom progressivo: L1 Context (sistemas e atores externos), L2 Container (unidades de deploy dentro de um sistema), L3 Component (módulos dentro de um container) e L4 Code (classes/funções). Para Blu × Omie paramos em L3 — L4 é “leia o código”: tudo em backend/app/ e omie-sdk-mock/ é Python tipado e diretamente navegável.
Source-of-truth: docs-site/public/architecture/workspace.dsl (Structurizr DSL). Renderizado nativo pelo viewer Spacerizr — pan, zoom, drill L1 → L2 dentro do próprio diagrama via controles do viewer.
Workspace interativo
Section titled “Workspace interativo”L1 · System Context — leitura
Section titled “L1 · System Context — leitura”Quem fala com o backend e por quê.
- Bubble → Backend: 1 trigger automático (workflow “Pedido criado” →
POST /v1/orders/{id}/sync-omie) + N botões de ops backoffice (retry,cancel-nf,mark-nf-manual,approve-billing-hold,override-day25,transfer-cc,use-cc). - Backend → Bubble: writeback via PATCH na Bubble Data API. Atualiza
status_pedido(3 valores) estatus_faturamento(9 valores) independentes. - Pagar.me → Backend: webhook direto (
POST /webhooks/pagarme) com validação HMAC. Bubble não recebecharge.*— handler vive no backend. - Brasil API: backend chama (não Bubble) por cache + rate-limit centralizado. Resultado CNPJ é escrito de volta no Bubble.
- Omie (AABC): motor fiscal. Backend orquestra UpsertCustomer · CreateOS · FaturarOS · EmitirNFSe · CancelarNFSe. LNG está fora do escopo da Versão Atual.
L2 · Containers — leitura
Section titled “L2 · Containers — leitura”Dentro do backend: FastAPI síncrono atende caminho feliz; ARQ worker processa billing deferido; SDK in-process centraliza throttle e recovery.
- FastAPI — async, lifespan re-inicializa state mutável a cada start (test gotcha: TestClient re-entry vazaria state senão).
- ARQ Worker — jobs em background. Billing scheduler decide quando emitir NF baseado em
billing_trigger+billing_scheduled_date. ⚠ Gap 2 do audit: ambas colunas ainda não existem emorder_sync_record(Alembic migration pendente). - Omie SDK — imports diretos no backend, não via HTTP. Throttle + REDUNDANT/HTTP 425 ban recovery centralizado num único ponto (
Lane Amergeada em PR #11). - Postgres — source-of-truth de side-effects do backend (
idempotency_cache,billing_schedule,order_sync_record,invoices,action_log). Bubble continua source-of-truth de business data. - Redis — ARQ queue + idempotency cache + rate-limit state.
L3 · Components — OrderToBilling flow (narrativa)
Section titled “L3 · Components — OrderToBilling flow (narrativa)”L3 de fato está em código. Os componentes-chave do fluxo principal:
| Componente | Local | Responsabilidade |
|---|---|---|
OrdersRouter | backend/app/routers/orders.py | POST /v1/orders/{id}/sync-omie + ops endpoints. Idempotency check via Redis. |
SyncService | backend/app/services/sync_service.py | Orquestra: lookup Bubble order → upsert Omie customer → create OS → writeback status. ⚠ Gap 4: escreve "NF emitida" (PT) em status. |
BillingDecider | backend/app/services/billing.py | decide_billing() pura função: PF imediato vs PJ dia 25 vs aguardando confirmação. |
InvoiceService | backend/app/services/invoice_service.py | emit_invoice() → FaturarOS + EmitirNFSe + writeback NF. ⚠ Gap 4: escreve "invoiced" (EN). Conflito com SyncService. |
BubbleClient | bubble-sdk/ | PATCH typed via codegen models. |
run_billing_job | backend/app/jobs/billing.py:85-106 | ARQ tick periódico. ⚠ Gap 1 do audit: não chama InvoiceService.emit_invoice ainda — loop deferido não fecha. |
Sync path (caminho feliz)
Section titled “Sync path (caminho feliz)”Bubble trigger → OrdersRouter → idempotency cache → SyncService → BubbleClient (lookup) → Omie SDK (UpsertCustomer + CreateOS) → BubbleClient (writeback status). Latência alvo < 3s. Falha aqui = 5xx, Bubble vê erro imediato.
Deferred path (billing dia 25 + retries)
Section titled “Deferred path (billing dia 25 + retries)”ARQ tick → run_billing_job → Postgres (SELECT FROM billing_schedule WHERE scheduled_at <= NOW()) → InvoiceService.emit_invoice → Omie SDK (FaturarOS + EmitirNFSe) → BubbleClient (writeback NF). ⚠ Atualmente: run_billing_job faz a query mas não chama InvoiceService (Gap 1). Implementação depende de Gap 2 (colunas billing_scheduled_date + billing_trigger).
Source & regeneração
Section titled “Source & regeneração”- Source canonical:
docs-site/public/architecture/workspace.dsl. Editar aqui. - Renderização: client-side via Spacerizr web component (
<spacerizr-viewer>). Zero build step — DSL é fetched no load. - Static export (opcional, pra deploy offline):
make c4(root Makefile) chama Spacerizr CLI pra gerar SVG. Ainda não wired — usarnpx spacerizr docs-site/public/architecture/workspace.dsl --export svgad hoc por enquanto. - Editing flow: edit
.dsl→ reload página → viewer re-fetcha + re-renderiza. Semmake c4necessário em dev.