Pular para o conteúdo principal

Visão geral da arquitetura

O WalletsApp é dividido em repositórios independentes, cada um com uma responsabilidade única. Nenhum deles acessa o banco de dados de outro diretamente — a comunicação entre eles é sempre por HTTP assinado.

Cliente (wallet-admin, wallet-cashier, app do participante, PDV)


wallet-edge ──── único ponto público

├── rota global ─────────────► wallet-control-api
│ (identidade, diretório, gestão)

└── rota regional (por eventId) ► wallet-api
(wallets, saldos, ledger daquele evento)

Os repositórios

RepositórioResponsabilidadeNunca faz
wallet-edgeÚnico ponto público. Valida JWT preliminarmente, aplica CORS/CSRF/Turnstile/rate limit, resolve se a rota é global ou regional e assina a chamada de origem.Não guarda dado nenhum; não é a autoridade final de autenticação.
wallet-control-apiIdentidade global (app.user_profiles), organizações, workspaces (= eventos), contas de PDV/caixa, diretório global de wallets.Nunca escreve saldo, ledger ou qualquer dado financeiro.
wallet-apiUma instância por região. Dono de app.wallets, app.wallet_balances, ledger.* daquele evento.Nunca decide identidade global nem organização/permissão.
wallet-adminFrontend de gestão.
wallet-cashierFrontend do caixa.

Por que "global" e "regional"

Um evento (workspace) é provisionado numa região física (home_region + cluster_key). Tudo que é financeiro daquele evento — wallets, saldos, ledger — mora no banco regional daquela região, não no banco global. Isso existe para que o dado financeiro fique fisicamente perto de onde o evento acontece, e para que uma região possa cair sem derrubar identidade/login globalmente.

A wallet-edge decide o roteamento pelo formato da URL:

  • /v1/auth/*, /v1/wallet/*, /v1/management/*, /v1/directory/* → sempre wallet-control-api (global). /v1/wallet/* reúne os recursos globais exclusivos do participante (descoberta de wallets, busca de evento por código), do mesmo jeito que /v1/management/* reúne os recursos exclusivos da gestão — o prefixo já diz de quem é o recurso.
  • /v1/events/{eventId}/* → resolvido pela região daquele eventId especificamente, via wallet-api regional.

Isso tem uma consequência prática: nunca aninhe um endpoint de descoberta global sob /v1/events/{eventId}/..., porque a Edge vai tentar tratar qualquer segmento ali como um eventId de verdade e tentar resolver a região dele. Por isso GET /v1/wallet/events/by-code/{code} (busca de evento por código) vive sob /v1/wallet, não sob /v1/events.

O diretório global de wallets

global.user_wallet_directory (em wallet-control-api) é um espelho de descoberta, nunca a fonte de saldo. Quando a wallet-api regional ativa uma wallet, ela enfileira um evento wallet.activated numa outbox própria (ops.outbox_events); um dispatcher em segundo plano (mesmo processo, setInterval, sem cron externo) envia isso pra wallet-control-api via uma chamada assinada com CONTROL_PLANE_SECRET. Veja Autenticação e perfis pra entender essa assinatura, e a seção do Participante pra ver GET /v1/wallet/wallets usando esse diretório.

Assinaturas internas — dois esquemas diferentes

EsquemaQuem assinaQuem verificaHeader da assinatura
Origin signaturewallet-edgeAPI de origem (InternalOriginGuard)X-Origin-Signature
Control-plane signaturewallet-control-apiwallet-api (as duas direções)ControlPlaneGuard no lado que recebeX-Control-Signature

O control-plane signature é simétrico: a mesma função assina dos dois lados, com o mesmo segredo (CONTROL_PLANE_SECRET). A wallet-control-api usa isso pra provisionar um evento na região; a wallet-api usa o mesmo esquema, na direção oposta, pra sincronizar o diretório de wallets.