Containers (C4 nível 2)¶
"Container" aqui é o termo C4 (unidade deployável), não necessariamente um container Docker. São três apps no monorepo, mas quatro processos do Garantora em execução: o apps/api sobe duas vezes, com a mesma imagem — uma como API HTTP (uvicorn), outra como worker da fila (arq, spec 0050).
flowchart TB
subgraph Cliente
Browser["Navegador\n(PWA instalável)"]
end
subgraph Garantora["Plataforma Garantora"]
Web["apps/web\nRefine v5 + React 19 + MUI v9\nbuild estático servido por nginx"]
Api["apps/api — API\nFastAPI + SQLAlchemy 2.x async\nRouter → Service → Repository → Model"]
Worker["apps/api — worker\narq (mesma imagem, outro comando)\nconsultas de crédito + jobs das 6h e 7h"]
Db[("PostgreSQL 16\ndados de negócio e\narquivos dos documentos")]
Redis[("Redis 7\nfila do arq")]
end
E2E["apps/e2e\nPlaywright — roda contra\no dev-remote, nunca contra prod"]
APIFULL["API Full\n(externo)"]
MAIL["SMTP\n(Mailpit / AWS SES)"]
Browser -->|"HTTPS, mesma origem\n(/app em local e dev-remote, / em produção)"| Web
Browser -->|"HTTPS /api/v1/...\nJWT Bearer"| Api
Api -->|"asyncpg"| Db
Api -->|"enfileira run_credit_check_job\n(depois do COMMIT)"| Redis
Worker -->|"consome a fila,\ncron 6h e 7h (America/Sao_Paulo)"| Redis
Worker -->|"asyncpg"| Db
Worker -->|"HTTPS"| APIFULL
Api -->|"HTTPS (token, extrato)"| APIFULL
Api -->|"SMTP"| MAIL
E2E -.->|"interage via browser,\nnunca chama a API direto"| Browser | Container | O que é | Onde vive o código |
|---|---|---|
| web | SPA Refine, empacotada pelo Vite e servida como estático por nginx (em local, o próprio Vite com hot reload). PWA instalável, com service worker. Exibe a versão publicada e o painel de Novidades (specs 0043 e 0044), cujas notas são os .md de apps/web/src/versoes/ embutidos no build. | apps/web/ |
| api | FastAPI sob /api/v1, Swagger em /api/docs, /health e /api/v1/health (os dois devolvem estado do banco, commit e versão da imagem). Uma transação por requisição (CommitRoute). | apps/api/app/ |
| worker | arq app.worker.WorkerSettings. Um job enfileirado — run_credit_check_job, um por item de consulta de crédito — e dois agendados: daily_credit_finance_job (6h: purga do payload bruto vencido, sincronização do extrato da API Full, acerto de preço) e daily_billing_overdue_job (7h: marca OVERDUE a cobrança vencida dos canais da própria garantidora). Sem retry automático (max_tries = 1). | apps/api/app/worker.py |
| PostgreSQL | Todo o dado de negócio, multi-tenant lógico. Os arquivos de documento também moram aqui (documents.content, LargeBinary) — não há object storage. | — |
| Redis | Só a fila do arq. Não é cache nem sessão. | — |
Subir só a API deixa o worker mudo — a consulta de crédito fica PENDING e os jobs agendados não acontecem, sem erro nenhum. Por isso o worker existe nos três ambientes (ver deployment.md). A API não depende do Redis para subir: o pool do arq é criado na primeira vez que algo é enfileirado (app/core/queue.py), e uma falha de enfileiramento marca aquela consulta como FAILED em vez de derrubar a requisição.
A ordem de implementação segue a seta do contrato: apps/api define o contrato (model → schema → repository → service → router), apps/web consome, apps/e2e prova o fluxo ponta a ponta. Ver CLAUDE.md da raiz, seção "Mudanças que atravessam os três apps".
Origem única, roteada por path — não há CORS entre web e api: o front chama /api/v1/... relativo, mesma origem, em todos os três ambientes. O que muda entre eles é quem roteia (Caddy em local e produção, nginx do host no dev-remote) e onde o app fica: sob /app em local e dev-remote, na raiz em produção (VITE_BASE_PATH=/) — ver deployment.md.
apps/e2e nunca é um "container" de produção — é a suíte de testes, e roda sempre contra o dev-remote, nunca contra produção nem contra um ambiente isolado montado à mão (ver CLAUDE.md da raiz, seção "O e2e roda contra o dev-remote, sempre").