Skip to content

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.

Carregando viewer C4…
Use os controles do viewer pra navegar entre views (L1 Context · L2 Containers). Pan = drag; zoom = scroll; reset = botão.

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) e status_faturamento (9 valores) independentes.
  • Pagar.me → Backend: webhook direto (POST /webhooks/pagarme) com validação HMAC. Bubble não recebe charge.* — 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.

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 em order_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 A mergeada 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:

ComponenteLocalResponsabilidade
OrdersRouterbackend/app/routers/orders.pyPOST /v1/orders/{id}/sync-omie + ops endpoints. Idempotency check via Redis.
SyncServicebackend/app/services/sync_service.pyOrquestra: lookup Bubble order → upsert Omie customer → create OS → writeback status. ⚠ Gap 4: escreve "NF emitida" (PT) em status.
BillingDeciderbackend/app/services/billing.pydecide_billing() pura função: PF imediato vs PJ dia 25 vs aguardando confirmação.
InvoiceServicebackend/app/services/invoice_service.pyemit_invoice() → FaturarOS + EmitirNFSe + writeback NF. ⚠ Gap 4: escreve "invoiced" (EN). Conflito com SyncService.
BubbleClientbubble-sdk/PATCH typed via codegen models.
run_billing_jobbackend/app/jobs/billing.py:85-106ARQ tick periódico. ⚠ Gap 1 do audit: não chama InvoiceService.emit_invoice ainda — loop deferido não fecha.

Bubble triggerOrdersRouter → idempotency cache → SyncServiceBubbleClient (lookup) → Omie SDK (UpsertCustomer + CreateOS) → BubbleClient (writeback status). Latência alvo < 3s. Falha aqui = 5xx, Bubble vê erro imediato.

ARQ tickrun_billing_job → Postgres (SELECT FROM billing_schedule WHERE scheduled_at <= NOW()) → InvoiceService.emit_invoiceOmie 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 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 — usar npx spacerizr docs-site/public/architecture/workspace.dsl --export svg ad hoc por enquanto.
  • Editing flow: edit .dsl → reload página → viewer re-fetcha + re-renderiza. Sem make c4 necessário em dev.