Pular para o conteúdo

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/src/blu_omie/ 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 endpoint manual (botão “Forçar sync agora” → POST /v1/orders/{id}/sync-omie) + endpoints pontuais de operador (retry, cancel-nf, mark-nf-manual). Endpoints approve-billing-hold, override-day25, transfer-cc, use-cc não existem no backend — essas ações viram edições de campo no Order Bubble (ADR-9).
  • Backend → Bubble: writeback via PATCH na Bubble Data API. Atualiza status_pedido (3 valores) e status_faturamento (9 valores) independentes.
  • Pagar.me → Bubble: webhook charge.* continua no Bubble (workflow legado HMAC + idempotência funcionando). Bubble muta Order.status_pedido = confirmado. Backend descobre via modified_date > cursor no próximo tick do reconciler (ADR-9).
  • 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).
  • SQLite — 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. Postgres disponível como acessório Kamal quando volume > 5 GB.
  • ARQ worker — sem Redis; fila persistida em SQLite + cron ARQ (sem broker externo na configuração atual).

L3 · Components — OrderToBilling flow (narrativa)

Seção intitulada “L3 · Components — OrderToBilling flow (narrativa)”

L3 de fato está em código. Os componentes-chave do fluxo principal:

ComponenteLocalResponsabilidade
OrdersRouterbackend/src/blu_omie/main.pyPOST /v1/orders/{id}/sync-omie + ops endpoints (retry, cancel-nf, mark-nf-manual). Idempotency check via SQLite cache.
SyncServicebackend/src/blu_omie/services/sync_service.pyOrquestra: lookup Bubble order → upsert Omie customer → create OS → writeback status. ⚠ Gap 4: escreve "NF emitida" (PT) em status.
BillingDeciderbackend/src/blu_omie/services/billing_service.pydecide_billing() pura função: PF imediato vs PJ dia 25 vs aguardando confirmação.
InvoiceServicebackend/src/blu_omie/services/invoice_service.pyemit_invoice() → FaturarOS + EmitirNFSe + writeback NF. ⚠ Gap 4: escreve "invoiced" (EN). Conflito com SyncService.
BubbleClientbubble-sdk/src/bubble_sdk/client.pyPATCH typed via codegen models.
run_billing_jobbackend/src/blu_omie/jobs/billing.pyARQ 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.