Princípios Essenciais de Engenharia para Sistemas Financeiros

Published on September 25, 2025

Se tem uma coisa que todo programador descobre cedo ou tarde é que mexer com dinheiro não é só código, é responsabilidade.

Em um e-commerce, se um bug repete um item no carrinho, o cliente se irrita — mas a loja pode estornar.

Mas em um sistema financeiro, se um bug duplica uma liquidação, o banco pode tomar um prejuízo milionário em segundos (e você vira o “nome da reunião” no dia seguinte 😅).

É por isso que os sistemas financeiros têm uma engenharia diferente: eles não são apenas APIs que salvam dados em tabelas, mas máquinas de precisão, onde cada centavo precisa ter origem, destino e motivo documentados.

Neste artigo, eu quero compartilhar princípios que aprendi na prática construindo e mantendo sistemas de crédito, cobrança, Pix e afins. São lições que todo engenheiro de software pode aplicar para entender por que, no mundo financeiro, imutabilidade, idempotência e auditoria não são buzzwords — são sobrevivência. 🙃

Inscreva-se gratuitamente!

Princípio nº 1: O Passado é Imutável

Antes de falarmos de bancos de dados, faturas ou regulamentações, precisamos falar de um conceito que vive no coração do nosso código: a Imutabilidade.

A Origem do Conceito: Da Palavra à Programação Funcional

A palavra "imutável" vem do Latim immutabilis: im- (prefixo de negação) + mutabilis (mutável, que pode ser mudado). A definição é literal: algo que não pode ser alterado.

Na engenharia de software, essa ideia não é nova. Ela é um dos pilares da programação funcional (PF), um paradigma popularizado por linguagens como Lisp e Haskell. A principal motivação da PF é escrever código mais previsível e livre de surpresas, e a imutabilidade é a sua principal ferramenta para isso.

A filosofia é simples: uma vez que um dado (um objeto, uma variável) é criado, seu estado nunca mais deve mudar. Se você precisa de uma versão modificada, você não altera o original; você cria um novo dado a partir do original com a alteração desejada.

Por que Precisamos nos Atentar a Isso no Código?

A resposta curta é: para evitar efeitos colaterais (side effects).

Um efeito colateral ocorre quando uma função, além de retornar um valor, modifica algum estado fora de seu escopo — como alterar um objeto que foi passado para ela como parâmetro. Isso transforma seu programa em um campo minado de bugs difíceis de rastrear.

Imagine um objeto usuario que é compartilhado por várias partes do seu sistema. Uma função A o utiliza para exibir o nome na tela. Ao mesmo tempo, uma função B é chamada para atualizar o e-mail desse usuário. Se a função B muta (altera) diretamente o objeto usuario original, a função A pode, de repente e sem aviso, passar a exibir um dado diferente ou se comportar de forma inesperada. Agora multiplique isso por centenas de funções e objetos e você tem o caos.

Trabalhar com imutabilidade no código nos força a uma disciplina que torna o software:

  • Previsível: Sem efeitos colaterais, o fluxo de dados se torna explícito e fácil de seguir.

  • Mais Fácil de Debugar: Se um dado está errado, você não precisa se perguntar "quem alterou isso?". Em vez disso, você pode rastrear a sequência de transformações que criou aquele novo dado.

  • Seguro para Concorrência: Em sistemas que executam múltiplas tarefas ao mesmo tempo (multithreading), garantir que ninguém está alterando um dado enquanto outro está lendo é um dos maiores desafios. Dados imutáveis resolvem essa classe de problemas de forma elegante.

Imutabilidade na Prática: O Exemplo em TypeScript

Vamos trazer a teoria para a prática com nosso exemplo de fatura, focando apenas no que acontece com os objetos na memória do programa.

// Um objeto representando o estado inicial de uma fatura
const faturaOriginal = { id: 1, valor: 500, status: "FECHADA" };

// ❌ JEITO ERRADO: Mutando o objeto. A informação original é destruída.
function estornarValorErrado(faturaParaEstornar) {
  faturaParaEstornar.valor = 400; // Alterando o objeto original!
  faturaParaEstornar.status = "AJUSTADA";
  return faturaParaEstornar;
}

// ✅ JEITO CERTO: Criando um novo objeto. O original é preservado.
function estornarValorCerto(faturaOriginal) {
  // Usamos o spread operator (...) para criar uma cópia e depois sobrescrevemos as propriedades
  const faturaCorrigida = {
    ...faturaOriginal,
    valor: 400,
    status: "AJUSTADA"
  };
  return faturaCorrigida;
}

No primeiro caso (ERRADO), a função estornarValorErrado recebe o objeto faturaOriginal e "rabisca por cima" de suas propriedades. O estado original (valor: 500) é perdido para sempre na memória. Qualquer outra parte do programa que tinha uma referência a esse objeto verá a mudança inesperadamente.

No segundo caso (CERTO), a função estornarValorCerto respeita a imutabilidade. Ela cria um objeto faturaCorrigida completamente novo, deixando o faturaOriginal intacto. Agora temos dois estados distintos e claros na memória. Criamos um histórico, em vez de corrompê-lo.

O Teste de Estresse da Imutabilidade: Cancelamentos e Estornos na Fatura Fechada

A teoria da imutabilidade é poderosa, mas como ela sobrevive ao caos do mundo real? Vamos para a trincheira com um cenário clássico: o cliente fez uma compra, a fatura fechou e, depois, ele cancela essa compra. E agora? A imutabilidade quebra? O valor simplesmente some do registro passado?

A resposta é um sonoro não. E é aqui que a robustez do princípio se revela. A Regra de Ouro do Mercado: Uma Fatura Fechada Não é Alterada. Ponto.

Pense na fatura fechada como um contrato selado ou um snapshot de dados imutável. É um registro consolidado daquele período de faturamento que já foi comunicado para múltiplos sistemas: a contabilidade do banco, a processadora de pagamentos (adquirente) e a bandeira (Visa/Mastercard). Alterar esse registro retroativamente criaria um caos de conciliação em toda a cadeia de pagamentos.

Então, como o mercado resolve isso? O sistema não executa um UPDATE ou DELETE em um registro passado. Em vez disso, ele insere (INSERT) um novo registro financeiro para representar a correção.

O cancelamento de uma compra, o recebimento de Cashback ou o estorno de um IOF (em compras internacionais) não altera a fatura fechada. Ele se torna um lançamento a crédito na sua próxima fatura — aquela que está atualmente aberta e recebendo novas transações.1

Vamos a um exemplo prático para ficar claro:

  • 05 de Outubro: Você compra um fone de R$ 500. A cobrança é registrada para a sua fatura de Outubro.

  • 10 de Outubro: Sua fatura de Outubro fecha com um total de, digamos, R$ 1.200 (incluindo o fone). Este registro de R$ 1.200 agora é considerado imutável.

  • 12 de Outubro: Você devolve o fone, e a loja aprova o cancelamento.

  • 13 de Outubro: A operadora do seu cartão recebe a confirmação do estorno. O que o sistema faz?

    • Ele não reabre ou modifica o registro da fatura de Outubro.

    • Ele gera um novo evento financeiro: um "Crédito de Confiança" ou "Estorno" no valor de R$ 500, associado à sua fatura de Novembro (a que está aberta).

O resultado final para o cliente:

  • Você vai pagar os R$ 1.200 da fatura de Outubro normalmente.

  • Na sua fatura de Novembro, antes mesmo de você fazer qualquer nova compra, já existirá um lançamento positivo: + R$ 500,00. Esse crédito irá abater dos seus novos gastos de Novembro. Se você gastar R$ 700 em Novembro, sua fatura fechará em apenas R$ 200.

O mesmo vale para o estorno de IOF: a cobrança original do imposto permanece no registro da fatura fechada, e um lançamento de crédito do mesmo valor é criado para a fatura seguinte.

Este cenário não quebra a imutabilidade; ele a reforça. O sistema respeita a integridade dos dados passados e, para corrigi-los, cria novos registros auditáveis no presente.

A história financeira, portanto, não é reescrita. Ela é continuamente expandida, com cada novo evento — seja uma compra ou um estorno — adicionando um novo fato a uma linha do tempo clara, transparente e, o mais importante, imutável.

Além do Código: A Imutabilidade como Cultura e Arquitetura

Já estabelecemos que o passado é imutável. Mas uma coisa é entender o conceito no conforto de um exemplo de código; outra, bem diferente, é resistir à tentação de quebrar essa regra quando o sistema está em chamas em uma sexta-feira à tarde.

É nesse momento que a imutabilidade deixa de ser um detalhe técnico e vira uma questão de cultura, processo e, em última instância, de sobrevivência para o negócio.

A Tentação do UPDATE em Produção: O Atalho para o Inferno

Agora, sejamos brutalmente sinceros. Todo desenvolvedor já esteve nessa situação: um bug, uma cobrança errada, um status que não virou. E a vozinha do mal sussurra no seu ouvido:

“Ih, o job rodou com o valor errado... deixa eu só dar um UPDATE rapidinho direto no banco de produção pra consertar. Ninguém vai notar.”

Você abre o DataGrip, digita o comando, aperta Ctrl+Enter e... ufa. O valor na tela está correto. Problema resolvido, deploy salvo. Certo?

Errado. O que você acabou de fazer foi plantar uma bomba-relógio no coração do sistema. No curto prazo, parece que funcionou. Mas no médio e longo prazo, começa o inferno:

  • Na segunda-feira, o time financeiro te liga desesperado: a conciliação com o parceiro de pagamentos não fecha. Aquele valor que você "corrigiu" não bate com o que foi reportado.

  • Na terça, o cliente abre um chamado no Reclame Aqui com prints: o extrato dele mudou magicamente de um dia para o outro. A confiança dele no seu sistema acabou de ir para o lixo.

  • Na quarta, um auditor interno te pergunta: "Pode me mostrar a trilha e a aprovação para essa alteração no lançamento XYZ?". A resposta é um silêncio constrangedor. Não existe trilha.

  • No mês seguinte, o regulador descobre a discrepância. Aquele UPDATE inocente de 30 segundos agora se transformou em um problema de reputação, regulatório e, potencialmente, de milhões.

O atalho rápido virou o caminho mais longo e doloroso.

Construindo a Prova de Balas: As Ferramentas da Imutabilidade

Se não podemos usar o atalho, qual é o caminho certo? É construir sistemas onde o UPDATE destrutivo seja arquiteturalmente impossível ou, no mínimo, muito difícil. A engenharia nos dá várias ferramentas para isso:

  • O Mantra do Append-Only: Esta é a filosofia central. Nada de apagar ou editar registros financeiros. Se algo está errado, você insere um novo registro que corrige o anterior. Um pagamento errado? Não se edita a linha original; cria-se uma nova transação (um estorno, um crédito) que anula o efeito da primeira.

  • A Borracha que não Apaga (Soft Deletes): Precisa "apagar" um registro? Jamais use o DELETE. Em vez disso, adicione uma coluna status ou cancelado_em e marque aquele registro como inativo. Ele continua lá para fins históricos e de auditoria, mas a sua aplicação sabe que não deve mais considerá-lo válido.

  • A Máquina do Tempo do Versionamento: Este padrão é para cenários onde a própria entidade financeira evolui. Não se aplica a um simples estorno de compra na fatura do cartão (que vira crédito na fatura seguinte), mas sim a casos como:

    • Renegociação de Dívida: fatura_v1 é a original, não paga. O sistema a marca como 'RENEGOCIADA' e cria um novo acordo_v1, com novas regras e parcelas. O passado não foi alterado, foi sucedido.

    • Correção de Faturas B2B: fatura_empresa_v1 é emitida com um erro. Ela é marcada como 'CANCELADA' e uma fatura_empresa_v2, com um novo número, é emitida corretamente, referenciando a original. O histórico completo da jornada daquele débito fica preservado.

  • O Livro do Cartório no Banco de Dados (Ledgers): Use tabelas de histórico ou estruturas de ledger. São tabelas projetadas para nunca serem alteradas, apenas receberem novas inserções. Elas frequentemente têm colunas como valid_from e valid_to, permitindo que você reconstrua o estado exato do sistema em qualquer ponto do passado.

  • A Verdade Absoluta do Event Sourcing: A forma mais pura de imutabilidade. O estado de uma entidade (como o saldo de uma conta) não é um número salvo em uma coluna. Ele é o resultado da aplicação de todos os eventos (créditos e débitos) que já aconteceram naquela conta desde sua criação. A trilha de auditoria e o estado são a mesma coisa.

A Verdade Desconfortável que Ninguém Gosta de Ouvir

Sejamos francos: imutabilidade é chata, mas necessária. 👀

Ela é o atrito, a burocracia que te impede de tomar aquele "atalho rápido" que salvaria seu deploy às 18h da sexta-feira. Ela te força a pensar mais, a planejar a correção, a criar um novo tipo de transação de ajuste.

Mas é essa "chatice" que garante que:

  • O cliente confie no extrato dele.

  • O auditor não te destrua na reunião trimestral.

  • O banco não quebre porque alguém tentou "consertar" uma inconsistência no susto.

É exatamente como o cartório: um processo que pode parecer lento, burocrático e às vezes irritante. Mas sem ele, qualquer um poderia falsificar um documento de imóvel e reescrever a própria história.

Sem a disciplina da imutabilidade, que se estende do código à operação, qualquer sistema financeiro vira exatamente isso: um caos sem memória confiável. E confiança é a única moeda que realmente importa.

Princípio nº 2: Idempotência é Sobrevivência

Se imutabilidade é a regra de ouro para proteger o passado, idempotência é o escudo contra o caos do presente.

Vamos ser realistas: em sistemas financeiros, não existe esse mundo perfeito em que cada requisição acontece uma única vez, na ordem correta, sem nunca dar erro. O que acontece na prática é muito mais caótico:

  • A internet oscila.

  • O app do cliente trava.

  • O servidor dá timeout.

  • O parceiro financeiro demora para responder.

  • O cliente fica ansioso e clica cinco vezes em “Pagar Boleto”.

E adivinha quem sofre com isso? O seu sistema. Se você não projetou as operações para serem idempotentes, cada um desses eventos vira uma dor de cabeça: pagamentos duplicados, lançamentos repetidos, estornos manuais, clientes furiosos e prejuízo no balanço.

O Tratamento de Duplicidades é a Regra

Em sistemas de pagamentos, a premissa é que falhas de comunicação e reprocessamentos vão acontecer. Por isso, os protocolos são desenhados para lidar com isso de forma segura.

No Pix, por exemplo, o tratamento de retransmissões é parte essencial do protocolo. Se o Banco A envia uma ordem de pagamento (pacs.008) e não recebe a confirmação (pacs.002) dentro do tempo esperado, ele deve reenviar a mesma mensagem. Isso não é um loop infinito e desesperado; é um processo controlado com regras de timeout e um número limitado de tentativas. A segurança reside no fato de que a mensagem retransmitida carrega o mesmo identificador único, o EndToEndId. O sistema de destino, ao ver aquele ID repetido, sabe que não é uma nova transação, e a processa apenas uma vez.

Em outros sistemas, a duplicidade surge de formas diferentes. No boleto, a falha é na ponta: um cliente pode pagar a mesma conta duas vezes por engano. No débito automático, o problema pode ser um job de processamento que falha no meio e é reiniciado.

Ou seja: a vida real é um festival de tentativas múltiplas, sejam elas do protocolo, do usuário ou de um sistema interno. Por isso, sistemas financeiros precisam ser projetados para suportar chamadas duplicadas sem causar efeitos colaterais.

Idempotência na Prática

Idempotência significa: não importa quantas vezes você executar a mesma operação, o resultado final precisa ser sempre o mesmo.

Se o cliente mandou pagar R$ 200 para a conta X:

  • Uma vez → débito de R$ 200.

  • Duas vezes (retransmissão) → ainda débito de R$ 200.

  • Dez vezes → ainda débito de R$ 200.

O sistema não pode sair debitando R$ 200, R$ 400, R$ 600 só porque recebeu a mesma instrução repetida. Na prática de engenharia, isso significa adotar mecanismos de deduplicação.

Estratégias Usadas no Mercado

  • Chave de Idempotência (Idempotency Key): Muito usada em APIs de pagamento (Stripe, Adyen, Mercado Pago). Cada requisição do cliente (POST, PUT) traz um cabeçalho com uma chave única. O servidor guarda o resultado da operação atrelado a essa chave. Se a requisição repetir com a mesma chave, o sistema não processa de novo — apenas devolve a resposta que já foi computada.

// Exemplo em pseudo-código
if (cache.hasKey(request.idempotencyKey)) {
  return cache.getResult(request.idempotencyKey);
} else {
  const result = processPayment(request);
  cache.save(request.idempotencyKey, result);
  return result;
}
  • EndToEndId no Pix: No arranjo do Pix, cada transação carrega um identificador único de ponta a ponta. Seu objetivo principal é garantir unicidade e rastreabilidade. Ele funciona como o CPF da transação: se um Pix não caiu, é com esse ID que bancos e reguladores localizam o evento. Como efeito colateral positivo, ele é a chave de idempotência natural do sistema, evitando duplicidades em cenários de retransmissão

  • Controle de Unicidade no Banco de Dados: Uso de constraints UNIQUE em colunas críticas (ex: transaction_id). Se duas threads tentarem gravar a mesma operação, só a primeira passa; a segunda recebe um erro controlado de violação de chave, que a aplicação sabe como tratar.

  • Locks Otimistas ou Pessimistas: Em sistemas de liquidação ou conciliação, é preciso controlar concorrência para garantir que a mesma operação não seja aplicada duas vezes. Pode ser feito via SELECT FOR UPDATE (pessimista) ou via versioning (otimista).

  • Mensageria com Deduplicação: Em arquiteturas assíncronas, a entrega de mensagens é, por padrão, at-least-once (pelo menos uma vez), o que significa que mensagens podem chegar repetidas. A aplicação consumidora precisa estar preparada para lidar com isso, usando uma das estratégias acima para deduplicar o processamento.

Determinismo: A Alma da Idempotência

No fundo, o que buscamos é determinismo: dado o mesmo input, o sistema precisa produzir sempre o mesmo output, sem surpresas. Se o cliente pediu para pagar o boleto 123 no valor de R$ 500, esse pedido não pode virar uma loteria em que cada nova chamada resulta em um débito adicional. Determinismo é o que dá previsibilidade. E previsibilidade é o que dá confiança.

E Quando Não Pensamos Nisso?

Histórias de terror não faltam:

  • Cliente que recebe o Pix duas vezes porque a deduplicação falhou.

  • Boleto pago em duplicidade, exigindo estorno manual.

  • Sistema de débito automático que rodou o job duas vezes, cobrando o cliente em dobro.

Cada falha dessas gera:

  1. Trabalho operacional extra (equipes de backoffice corrigindo manualmente).

  2. Desgaste com o cliente (ninguém esquece quando o banco cobra em duplicidade).

  3. Risco regulatório (o BACEN pode punir reincidências graves).

A Verdade Nua e Crua

Idempotência é um daqueles conceitos que parecem detalhe técnico, mas, na prática, são questão de sobrevivência. O cliente nunca vai aceitar a desculpa: “Ah, foi porque o sistema chamou o endpoint duas vezes.”

Ele só vai ver o débito duplicado e pensar: “Meu banco não é confiável.” E confiança, em sistemas financeiros, é tudo.

“Se o cliente nota duas cobranças, ele não pensa em idempotência: ele pensa em incompetência.”

Princípio nº 3: Consistência é o Chão, Disponibilidade é o Teto

Agora vamos para o coração da fera, a escolha de todo arquiteto de software financeiro: Consistência vs. Disponibilidade.

Se idempotência era sobre não processar a mesma coisa duas vezes, consistência é sobre garantir que o estado do sistema faça sentido o tempo todo. É a garantia de que o saldo na conta é o saldo, e não uma versão aproximada, otimista ou desatualizada dele.

O Dilema do Mundo Real: O Pagamento que Demora

Imagine a cena: seu usuário abre o app para pagar um boleto de R$ 500. O saldo dele é de R$ 501. Ele clica em "Pagar". A rodinha começa a girar... e gira... e gira.

O que está acontecendo por trás das cortinas? Nosso sistema está, provavelmente, travando uma batalha épica para garantir a consistência. Ele precisa:

  1. Verificar se o saldo de R$ 501 é real e está disponível.

  2. Travar esse saldo para que nenhuma outra operação (um débito automático, outra compra no cartão) o consuma no mesmo instante.

  3. Efetivar o débito de R$ 500.

  4. Liberar a trava, deixando o novo saldo de R$ 1.

  5. Confirmar o pagamento do boleto.

Essa "rodinha girando" é o sintoma da Disponibilidade sendo sacrificada. O sistema não está 100% disponível para dar uma resposta instantânea. Por quê? Porque ele está priorizando a Consistência.

A alternativa seria um pesadelo: o sistema, para ser super rápido (alta disponibilidade), poderia ler o saldo de R$ 501, e enquanto processa o boleto, uma outra transação de Pix de R$ 10 entra e é aprovada. Resultado: o boleto é pago, o Pix é pago, e a conta do cliente fica com um saldo negativo de R$ 9. O sistema mentiu sobre a realidade financeira. E isso, no mundo do dinheiro, é o pecado original.

Agora, você pode estar pensando: "Mas e o cheque especial? Os bancos deixam o saldo ficar negativo!". Perfeito. Mas a pegadinha é que, para decidir se o cliente pode entrar no cheque especial de forma controlada, o sistema ainda precisa daquele mesmo processo de trava e consistência! Sem ele, o sistema não saberia se o saldo final seria -R$ 9 (dentro do limite) ou -R$ 999 (muito além do limite), e poderia aprovar uma operação que levaria a uma perda gigantesca. A consistência é o que permite ao banco tomar uma decisão de risco de forma segura.

O Segredo dos Bancos Modernos

Então os sistemas bancários são lentos o tempo todo? Não exatamente. A verdadeira maestria da arquitetura financeira está em aplicar essa consistência rigorosa apenas onde ela é inegociável, adotando um modelo híbrido.

  • O Core Transacional (O Cofre): No momento em que o dinheiro realmente muda de mãos – o pagamento do boleto, a transferência via Pix, a compra no cartão – o sistema vai obrigatoriamente buscar a informação no "chão". Ele opera no modo CP (Consistência > Disponibilidade). Ele vai usar travas, filas e tudo o que for preciso para garantir que a operação seja atômica e correta. Se isso levar 300 milissegundos a mais, que seja.

  • As Camadas de Experiência (A Vitrine): Mas e o saldo que aparece no topo da tela inicial do seu app? Aquele que você consulta rapidamente? Essa informação não precisa, necessariamente, vir do "cofre" a cada segundo. Ela pode vir de uma cópia de leitura mais rápida, um cache que opera no modo

  • AP (Disponibilidade > Consistência). Nesse caso, o sistema aceita uma "consistência eventual". Se o saldo na tela inicial estiver 1 ou 2 segundos desatualizado, não há problema. Mas no instante em que você clica em "Pagar", o sistema muda de postura e vai direto ao cofre para garantir a verdade absoluta.

A inteligência do design está em saber quando agir como um contador paranoico e quando agir como um servidor de cache ultrarrápido. E essa decisão é o que separa um sistema financeiro robusto e escalável de um monólito lento ou, pior, de um sistema rápido e perigosamente inconsistente.

Entra em Cena o Teorema CAP: A Lei da Física dos Sistemas Distribuídos

Todo programador que trabalha com sistemas distribuídos (ou seja, basicamente todo mundo hoje em dia) já ouviu falar do Teorema CAP. Ele não é uma "boa prática", é uma lei, como a gravidade.

Ele diz que, de três garantias, você só pode escolher duas em um sistema distribuído que sofra uma falha de rede:

  • Consistency (Consistência): Todos os nós do sistema enxergam a mesma versão dos dados ao mesmo tempo. Uma leitura sempre retorna o dado mais recentemente escrito.

  • Availability (Disponibilidade): Toda requisição recebe uma resposta (que não seja um erro), mesmo que alguns nós do sistema estejam fora do ar. O sistema está sempre "de pé".

  • Partition Tolerance (Tolerância a Partições): O sistema continua funcionando mesmo que a comunicação entre seus nós seja interrompida (uma "partição" de rede).

A pegadinha? A vida real garante que partições de rede vão acontecer. O cabo de rede vai ser desconectado, o data center vai perder conexão. Então, a Tolerância a Partições (P) não é uma escolha, é um fato da vida.

A verdadeira escolha que você, programador, precisa fazer é entre C e A.

  • Sistemas AP (Availability + Partition Tolerance): Priorizam a disponibilidade. Se um nó não consegue falar com o outro para saber o dado mais recente, ele responde com o melhor dado que tem no momento. É melhor responder com um dado talvez antigo do que não responder.

    • Exemplo: Uma rede social. Se você não vê o último post de um amigo por alguns segundos, tudo bem. O feed carregar é mais importante que ele estar 100% atualizado.

  • Sistemas CP (Consistency + Partition Tolerance): Priorizam a consistência. Se um nó não consegue garantir que tem a versão mais correta e atualizada do dado, ele recusa a operação. É melhor dar um erro ou fazer o cliente esperar do que retornar um dado inconsistente.

    • Exemplo: Qualquer sistema de liquidação financeira.

Princípio fundamental: Em uma transação financeira, a consistência vale mais que a experiência instantânea. Um Pix que demora 3 segundos é irritante. Um Pix que debita sua conta mas não chega no destino (ou chega duas vezes) destrói a confiança.

Estratégias para Garantir a Consistência

Então, como as instituições financeiras fazem isso na prática? Elas usam um arsenal de ferramentas e estratégias testadas em batalha.

  1. Locks Pessimistas no Banco de Dados (SELECT FOR UPDATE)

    • Como funciona: É a estratégia mais direta. Antes de modificar uma linha crítica (como o saldo de uma conta), o sistema diz ao banco de dados: "Ei, vou mexer nesta linha. Tranque-a e não deixe mais ninguém nem mesmo ler o valor dela até eu terminar e confirmar (commit)".

    • Visão do programador: É literalmente uma linha de código a mais no seu SQL. A SELECT ... FOR UPDATE garante um bloqueio exclusivo.

    • Trade-off: É a forma mais forte de consistência, mas também a que mais prejudica a performance e a concorrência. Se muitas transações tentam acessar a mesma conta, elas formam uma fila, esperando a liberação do lock. É por isso que é usado cirurgicamente, apenas no momento exato da liquidação.

  2. Transações ACID e Níveis de Isolamento Fortes

    • Como funciona: O bom e velho banco de dados relacional (Postgres, Oracle, etc.) é o melhor amigo da consistência. As garantias ACID (Atomicidade, Consistência, Isolamento, Durabilidade) são a base. Em especial, o Isolamento é crucial. Configurar o nível de isolamento para SERIALIZABLE ou REPEATABLE READ garante que uma transação não veja a "sujeira" (dados não commitados) de outra.

    • Visão do programador: É entender profundamente como seu banco de dados funciona e não usar os níveis de isolamento mais baixos (como READ UNCOMMITTED) em operações críticas só para ganhar performance.

  3. Two-Phase Commit (2PC - "Commit em Duas Fases")

    • Como funciona: E quando a transação envolve mais de um sistema, como debitar de um banco de dados e registrar em um sistema de liquidação separado? O 2PC é um protocolo para garantir consistência distribuída.

      • Fase 1 (Votação): O coordenador da transação pergunta a todos os participantes: "Vocês estão prontos para commitar? Prometem que não vão falhar?". Todos precisam responder "sim" e se preparar.

      • Fase 2 (Commit): Se todos disseram "sim", o coordenador manda a ordem: "Ok, podem commitar!". Se alguém falhou ou disse "não", ele manda: "Abortem tudo!".


      Trade-off:
      Pense no 2PC como uma cerimônia de casamento de altíssima segurança, onde o “sim” é irreversível. O coordenador é o juiz, e os sistemas participantes são os noivos.

      • Fase 1 (A Promessa): O juiz (coordenador) pergunta a cada noivo (participante) se eles aceitam o compromisso. Ao dizerem “sim”, eles não estão apenas concordando; eles estão se trancando em um estado de “prometido”. Eles não podem mais sair correndo do altar ou aceitar outra proposta. Seus recursos (suas vidas!) estão bloqueados.

      • Fase 2 (A Declaração): O juiz então oficializa o casamento para o mundo.

      O problema fatal acontece se o juiz desmaiar depois que ambos disseram “sim”, mas antes de declará-los casados. Os noivos ficam parados no altar. Eles não estão solteiros, mas também não estão oficialmente casados. Estão em um limbo, com seus recursos (seu estado civil) travados, esperando uma decisão que não vem. Para resolver, seria preciso encontrar um novo juiz que pudesse consultar as anotações do anterior para decidir se conclui a cerimônia ou se anula tudo.

      É exatamente por isso que o 2PC é considerado frágil: a queda do coordenador em um momento crítico cria um cenário de bloqueio indefinido que é um pesadelo para resolver em produção, exigindo intervenção manual e complexa. Ele garante a atomicidade, mas a um custo muito alto de disponibilidade e resiliência (para entender alternativas leia a nota ao rodape).2

O Modelo Híbrido: O Segredo dos Bancos Modernos

Nenhum sistema financeiro moderno é puramente CP ou puramente AP. Eles são inteligentes. Eles aplicam o rigor da consistência onde o dinheiro realmente muda de mãos e relaxam para a disponibilidade nas camadas de experiência do usuário.

  • O Core Transacional (Liquidação): É o cofre. Aqui, tudo é CP. Usam-se locks pessimistas, transações seriais, tudo para garantir que o livro-razão (ledger) seja perfeito. Se essa parte ficar lenta ou indisponível por alguns segundos para evitar inconsistência, que assim seja. O prejuízo de um erro aqui é grande demais.

  • Os Serviços Periféricos (Experiência): São as vitrines da loja. O extrato, o histórico de transações, o envio de notificações. Aqui, o sistema pode ser AP. Ele lê de réplicas do banco de dados, usa caches, sistemas de mensageria (como Kafka). Se o extrato do seu app demorar 2 segundos para mostrar o último Pix que você fez, não é o fim do mundo. É uma "consistência eventual". O sistema eventualmente ficará consistente, mas ele prioriza te dar uma resposta rápida agora.

Essa separação é a estratégia mais inteligente e realista: ser paranoico com a consistência no centro da operação e focar na disponibilidade nas bordas.

Como programador, seu trabalho é saber em que parte do sistema você está. Você está mexendo no cofre ou na vitrine? A resposta para essa pergunta define se seu código deve fazer o cliente esperar ou arriscar mostrar um dado um segundo desatualizado. E no mundo das finanças, essa é uma das decisões mais importantes que você vai tomar.

Princípio nº 4: Auditoria como Feature, não é algo opcional!

Vamos ser brutalmente honestos. Para muitos de nós, programadores, "log" é aquela coisa que a gente cospe no console pra debugar um problema em produção. É um monólogo caótico de INFO, WARN e ERROR que a gente só olha quando a casa já está pegando fogo. A gente pensa em log como um curativo, um remédio para o sintoma.

Em um sistema financeiro, essa mentalidade não é só errada, é perigosa.

Aqui, a trilha de auditoria não é um log de debug. É a caixa-preta do avião. É um registro imutável e detalhado de cada evento significativo que acontece no sistema. Não é algo que você adiciona depois porque o time de Compliance pediu. É um requisito funcional, uma feature tão importante quanto a própria transferência de dinheiro.

O princípio é simples e inegociável: cada centavo precisa de um "por quê" e um "quando" rastreáveis.

A Grande Diferença: Log de Debug vs. Trilha de Auditoria

Para a coisa ficar clara, vamos separar os dois mundos:

  • Log de Debug:

    • Propósito: Ajudar o desenvolvedor a entender o fluxo do código.

    • Audiência: Você e seu time de engenharia.

    • Formato: Geralmente texto livre, bagunçado. Ex: “Entrou no método de pagamento, valor: 100.50”.

    • Ciclo de Vida: Temporário. Pode ser sobrescrito, rotacionado, deletado.

    • Valor de Negócio: Indireto. Ajuda a consertar bugs.

  • Trilha de Auditoria:

    • Propósito: Prover um registro formal e legal de todas as ações. É evidência.

    • Audiência: Suporte ao cliente, time de Risco, Compliance, auditores externos (como o Banco Central) e, em última instância, a Justiça.

    • Formato: Altamente estruturado (JSON é seu melhor amigo aqui). Ex:

{
   "timestamp":"...",
   "event_type":"ACCOUNT_DEBITED",
   "actor":{
      "user_id":123,
      "origin_ip":"..."
   },
   "entity":{
      "account_id":456
   },
   "state_change":{
      "balance_before":1000.00,
      "balance_after":800.01,
      "amount":199.99
   },
   "context":{
      "transaction_id":"...",
      "correlation_id":"..."
   }
}
  • Ciclo de Vida: Permanente e imutável. Deve ser armazenado em local seguro por anos (a regulamentação exige!).

  • Valor de Negócio: Direto. Constrói confiança, mitiga risco, resolve disputas e cumpre a lei.

Seu console.log("deu pau") não serve para explicar a um auditor por que um saldo ficou negativo. A trilha de auditoria, sim.

"Logar Tudo, Sempre": O que significa "Tudo"?

"Logar tudo" não é sair cuspindo cada linha de código. É sobre registrar cada evento de negócio com significado. Em uma instituição financeira, isso se resume a algumas perguntas fundamentais:

  1. O QUÊ aconteceu? (O Evento e a Mudança de Estado)

    • Não basta dizer "o pagamento foi aprovado". Você precisa registrar a mudança de estado completa. O "antes" e o "depois".

    • Exemplo: Para um débito, você precisa registrar o saldo antes da operação e o saldo depois. Se um auditor (ou um cliente) questionar a transação, você tem a prova matemática de que o saldo foi calculado corretamente. Sem o "antes", a prova fica incompleta.

  2. QUEM fez? (O Ator)

    • Toda ação precisa de um dono. Foi o cliente ID 123 pelo aplicativo? Foi o agente de suporte ID 789 pela ferramenta de backoffice? Foi um SYSTEM_JOB que rodou de madrugada?

    • Essa rastreabilidade é crucial para investigações de fraude. Se uma operação suspeita foi feita, a primeira pergunta é: quem estava autenticado naquela sessão?

  3. QUANDO e ONDE aconteceu? (O Contexto)

    • Timestamp: Preciso, em UTC para evitar dores de cabeça com fuso horário.

    • Origem: Endereço de IP, geolocalização, User-Agent do navegador, ID do dispositivo móvel. Se uma conta de um cliente de Campinas tem uma transação originada de um IP do leste europeu, o sistema de fraude precisa saber disso.

  4. POR QUÊ aconteceu? (A Causa e a Correlação)

    • Em um mundo de microsserviços, uma única ação do usuário (como "pagar a fatura") pode gerar uma cascata de dezenas de eventos em sistemas diferentes. Como você conecta tudo isso?

    • Correlation ID: Gere um ID único no início da requisição (no API Gateway, por exemplo) e propague-o em todas as chamadas de serviço subsequentes e em cada linha de log/auditoria. Quando der um problema, você pode filtrar por esse ID e ver a história completa daquela transação, ponta a ponta, através de todos os microsserviços. É como dar um número de RG para cada requisição.

Estratégias Confiáveis: Como Implementar Isso Sem Enlouquecer

Construir uma trilha de auditoria robusta não precisa ser um pesadelo. A indústria já tem padrões para isso.

  1. Logging Estruturado e Centralizado (O Mínimo Viável)

    • A Prática: Abandone logs em texto puro. Use JSON. Sua linguagem de programação com certeza tem bibliotecas para isso (Serilog para .NET, Logback para Java, Winston para Node.js).

    • A Infraestrutura: Envie esses logs estruturados para uma plataforma centralizada de logging, como o ELK Stack (Elasticsearch, Logstash, Kibana), Splunk ou Datadog. A ideia é ter um único lugar onde você pode buscar, filtrar e criar dashboards sobre tudo o que acontece no seu ecossistema. grep em 20 servidores diferentes não é uma estratégia, é um ato de desespero.

  2. Change Data Capture (CDC) com "Shadow Tables" (A Estratégia Pragmática)

    • A Prática: Para cada tabela crítica no seu banco de dados (ex: contas), crie uma tabela espelho (contas_historico). Use triggers de banco de dados ou ferramentas como Debezium para que toda INSERT, UPDATE ou DELETE na tabela principal gere automaticamente uma nova linha na tabela de histórico, registrando o dado antigo, o dado novo, quem fez e quando.

    • Vantagem: Desacopla a lógica de auditoria da sua aplicação principal. O desenvolvedor do serviço de contas não precisa se lembrar de logar tudo; a auditoria acontece a nível de banco de dados, de forma transparente e garantida.

  3. Event Sourcing

    • A Prática: Essa é a mudança de paradigma mais radical. Em vez de armazenar o estado atual das coisas (ex: o saldo atual da conta é R$ 50), você armazena a sequência completa de eventos que levaram a esse estado.

    • Exemplo: [Conta Criada com saldo 0], [Depósito de R$100], [Compra de R$20], [Pagamento de Fatura de R$30]. O saldo atual (R$ 50) é apenas uma consequência, uma projeção desses eventos.

    • Vantagem: A trilha de auditoria É o seu sistema. Não há como o estado e a auditoria ficarem inconsistentes, porque eles são a mesma coisa. É a fonte da verdade mais pura que existe.

    • Desvantagem: É um padrão de arquitetura complexo (geralmente anda de mãos dadas com CQRS) e exige uma forma de pensar diferente. Não é para qualquer projeto, mas para o core de um sistema de ledger, manter o a jornada do usuário é essencial.

Por Que Isso Salva a Sua Pele (e seu Emprego)

No final do dia, uma trilha de auditoria impecável é o que permite que você durma à noite. É ela que vai responder às perguntas inevitáveis:

  • Do Suporte: "O cliente disse que o Pix não caiu. O que aconteceu?"

  • Do Time de Fraude: "Temos uma transação suspeita. Qual a origem e os passos exatos que o fraudador deu?"

  • Do Auditor do Banco Central: "Me forneça um relatório de todas as operações acima de X valor, autorizadas pelo gerente Y, no último trimestre."

  • De Você Mesmo, Daqui a Seis Meses: "Meu Deus, por que esse saldo está assim? O que aconteceu com essa conta?"

Sem as respostas para essas perguntas, seu sistema não é apenas um sistema com bugs. É um sistema que não é confiável. E em finanças, a falta de confiança não é um problema técnico, é uma sentença de morte.

Inscreva-se gratuitamente para receber novas postagens e apoiar meu trabalho.

Princípio nº 5: Precisão Matemática é Inegociável

Em sistemas financeiros, não existe "quase". Um cálculo tem que ser exato, repetível e, acima de tudo, estar em conformidade com a lei.

O princípio aqui é direto e assustador: 1 centavo fora de lugar, repetido em escala, vira fraude ou multa.

O Pecado Capital: Usar float ou double para Dinheiro

Todo programador, em algum momento da carreira, já caiu nesta armadilha. Você abre o console do navegador e digita:

0.1 + 0.2
// Resultado esperado: 0.3
// Resultado real: 0.30000000000000004

O que é essa aberração? É a consequência de como os computadores representam números de ponto flutuante (tipos float e double) usando o padrão IEEE 754. Eles trabalham em binário (base 2), e muitas frações decimais simples (como 0.1) não têm uma representação binária finita e exata. É como tentar escrever 1/3 como um decimal finito; você acaba com 0.33333...

Para a maioria das aplicações (jogos, gráficos, ciência), essa pequena imprecisão é irrelevante. Para dinheiro, é um desastre. Imagine essa diferença de 0.00000000000000004 sendo somada ou subtraída em milhões de transações de juros de cartão de crédito. Ao final do dia, o balanço da empresa não vai fechar. Alguém ganhou ou perdeu frações de centavos, e isso, em contabilidade, é o apocalipse.

A Matemática de Precisão Decimal

Para resolver isso, a computação financeira se baseia em tipos de dados de Decimal de Precisão Arbitrária (também conhecidos como BigDecimal ou Numeric).

  • Como funciona? Em vez de representar o número em binário de ponto flutuante, esses tipos geralmente o armazenam como um número inteiro grande e um fator de escala. Pense em 199.99 sendo armazenado internamente como o inteiro 19999 e uma instrução para colocar a vírgula duas casas da direita. Isso preserva a exatidão decimal que usamos no mundo real.

  • Suporte Nativo: Muitas linguagens usadas no backend financeiro tratam isso como um cidadão de primeira classe:

    • Java: BigDecimal

    • C#: decimal

    • Python: O módulo Decimal

    • Ruby: BigDecimal

    • Bancos de Dados: Tipos NUMERIC(precision, scale) ou DECIMAL são o padrão para colunas de dinheiro.

O Desafio em TypeScript/JavaScript

No ecossistema JavaScript, a situação é mais delicada. Nativamente, todos os números são do tipo number, que é um double de 64 bits. Não existe um tipo decimal nativo.

Tentar contornar isso "multiplicando por 100 e trabalhando com centavos como inteiros" é uma gambiarra perigosa. Ela funciona para somas e subtrações simples, mas quebra espetacularmente em cálculos de juros, amortização ou qualquer operação que envolva multiplicação e divisão complexas.

Como trabalhar com decimal em TypeScript da forma correta?

Você deve usar uma biblioteca robusta e bem testada pela comunidade. As mais confiáveis são:

  1. Decimal.js (ou decimal.js-light): Geralmente a mais recomendada. É completa, configurável (precisão, regras de arredondamento) e tem uma API clara.

  2. Big.js: Mais leve e rápida, mas com menos funcionalidades. Ótima para casos de uso mais simples.

  3. bignumber.js: Outra biblioteca muito popular e poderosa.

Exemplo prático em TypeScript:

import { Decimal } from 'decimal.js';

// O JEITO ERRADO (usando number nativo)
const valorErrado1 = 0.1;
const valorErrado2 = 0.2;
const resultadoErrado = valorErrado1 + valorErrado2;
console.log(resultadoErrado); // 0.30000000000000004

// O JEITO CERTO (usando Decimal.js)
// Note que passamos os valores como string para garantir que não haja perda de precisão na entrada
const valorCerto1 = new Decimal('0.1');
const valorCerto2 = new Decimal('0.2');
const resultadoCerto = valorCerto1.plus(valorCerto2);
console.log(resultadoCerto.toString()); // "0.3" - Exato!

Números ou Strings para Dinheiro em APIs JSON?

Esta é uma das grandes discussões em design de APIs financeiras. Se o JSON suporta o tipo number, por que a prática de mercado insiste em usar string para representar dinheiro, como em {"valor": "199.99"}?

A resposta é design defensivo. A decisão não é sobre o que é semanticamente "puro", mas sobre o que é mais seguro em um ecossistema complexo e heterogêneo.

O risco é a "contaminação por ponto flutuante": no momento em que um sistema no caminho interpreta seu número como um double, a precisão original pode ser perdida para sempre. Os culpados mais comuns são:

  1. Clientes em JavaScript: Se sua API retorna {"valor": 199.99}, o JSON.parse() no navegador ou no Node.js irá, inevitavelmente, criar um number do tipo double. A precisão já foi corrompida na chegada. Qualquer cálculo feito no frontend a partir dali carregará a imprecisão.

  2. Sistemas Intermediários: Uma API Gateway, um serviço de cache ou uma plataforma de logging podem processar seu JSON no meio do caminho. Se essas ferramentas não forem configuradas para lidar com decimais de alta precisão, elas podem ler o number e reescrevê-lo, potencialmente alterando seu valor em nível binário.

  3. Consumidores Poliglotas: Você não tem controle sobre a stack de tecnologia de todos que consumirão sua API. Uma equipe pode usar uma linguagem que trata números JSON como double por padrão.

A representação como string é inequívoca. "199.99" é um texto. Ela força o desenvolvedor do outro lado a fazer uma conversão explícita e intencional para um tipo decimal seguro em sua linguagem, em vez de aceitar passivamente um number que já pode estar "contaminado".

Então apenas reforçando:

  • Para APIs públicas ou ecossistemas heterogêneos, usar strings para valores monetários é o padrão-ouro da indústria para garantir máxima segurança e interoperabilidade.

  • Para APIs internas e controladas, onde você garante que todo o ecossistema (produtor e consumidor) trata números JSON com tipos decimais de forma consistente, o uso do tipo number é viável, mas exige maior governança.

As Regras do Jogo: O que o BACEN Exige?

Beleza, agora a coisa fica séria. Pensa no Banco Central como se fosse o arquiteto de uma API que você é obrigado a consumir. Não dá para inventar os parâmetros ou mandar os dados do jeito que você quer. A matemática do seu sistema financeiro tem um "contrato" a seguir, e fugir dele não gera um 400 Bad Request, gera multa.

Vamos ver os "endpoints" mais críticos dessa API regulatória.

1. O "Preço de Verdade": Custo Efetivo Total (CET)

Sabe o "preço de verdade" de um empréstimo ou financiamento? É isso que é o CET. Não é só aquela taxa de juros bonitinha que o marketing adora anunciar. O BACEN, que não é bobo nem nada, exige que você mostre TUDO o que o cliente vai pagar, de forma percentual. Isso inclui os juros, claro, mas também todas as tarifas, o IOF, seguros e até o custo do cafezinho, se ele for obrigatório na operação.

A documentação oficial? A "bíblia" que você precisa ter aberta na sua segunda tela é a Resolução CMN Nº 5.113, de 21 de dezembro de 2023.

O que isso significa pro seu código? Você terá que implementar a fórmula de juros compostos que o BACEN define para achar essa taxa. É uma conta que precisa ser cravada, porque pode ter certeza que o fiscal do outro lado vai rodar o mesmo cálculo para ver se bate.

2. Arredondamento: O Math.round() Proibido

Agora, um clássico onde muito dev júnior escorrega. Primeira regra do clube da programação financeira: esquece o Math.round() padrão da sua linguagem. Ele simplesmente não serve aqui.

No Brasil, a regra do jogo para arredondar dinheiro se chama ABNT NBR 5891. É o único round() que o BACEN confia. O resumo da ópera é:

  • Se o dígito a ser jogado fora for de 0 a 4, beleza, fica como está.

  • Se for de 6 a 9, arredonda pra cima.

  • A pegadinha está no 5: Se o número a ser descartado é exatamente 5, você não arredonda pra cima sempre. Você arredonda para o algarismo par mais próximo. Por exemplo, 2,35 vira 2,4, mas 2,45 também vira 2,4.

Por que essa complicação? É um detalhe nerd genial para evitar viés estatístico. Em milhões de operações, o arredondamento tradicional "sempre para cima no 5" faria o banco (ou o cliente) ganhar/perder dinheiro. Arredondar para o par mais próximo equilibra o jogo.

3. Quitação Antecipada: O Cliente Quer Pagar Antes, e Agora?

Outra situação batata: o cliente juntou uma grana e quer quitar o financiamento. Ele pode? Sim. Ele tem desconto? Com certeza.

E quem manda nisso é a Resolução CMN Nº 5.112, de 21 de dezembro de 2023. Ela garante que o cliente tem o direito de ter os juros das parcelas futuras abatidos.

A lógica é justa: se o banco não vai mais correr o risco daquele tempo todo, você não tem que pagar os juros correspondentes.

O que isso significa pro seu código? Seu sistema precisa ter uma função para "trazer a valor presente" as parcelas futuras. A conta usa a mesma taxa de juros do contrato, e, de novo, a matemática financeira tem que ser exata, sem margem para interpretação.

4. Transparência: O "Espírito da Lei"

Por fim, um tema que costura tudo isso: transparência. Não adianta fazer a matemática certa se você a exibe de um jeito que confunde o cliente.

O BACEN não tem uma única regra para isso, mas sim um princípio que aparece em todas as normas que a gente citou. As taxas precisam ser claras (mensal, anual, CET), os custos precisam estar discriminados, e o cliente precisa conseguir, de forma fácil, entender o que está pagando.

É a camada de "UX" da regulação. Seu sistema não precisa só ser matematicamente perfeito, ele precisa ser honesto na apresentação.

E a Norma Internacional? A ISO 20022 como "Contrato Universal"

Até agora, falamos sobre garantir a precisão dentro do nosso sistema. Mas de que adianta nossa calculadora ser perfeita se, ao enviar o resultado para outro banco, a mensagem se perde na tradução?

Antes de padrões como a ISO 20022, o mundo financeiro era uma Torre de Babel. Cada banco, cada sistema, tinha seu próprio formato de arquivo, seu próprio jeito de representar uma data ou um valor. A integração era um pesadelo de parsers customizados, e cada um desses "tradutores" era um ponto de falha em potencial para a precisão dos dados.

A ISO 20022 chega para ser o "dicionário global" das finanças. Olhando para o cenário global, ela é o padrão que define a troca de mensagens financeiras (o Pix, por exemplo, é baseado nela). A norma não dita como calcular, mas dita como representar e transmitir os dados de forma inequívoca.

O que isso significa na prática para o seu código?

  1. Fim da Ambiguidade (Além do Dinheiro): A norma define, para cada moeda, o número de casas decimais que ela suporta (ex: 'BRL' tem 2, 'USD' tem 2, mas 'CLF', um índice chileno, tem 4). Mas vai muito além: ela padroniza tudo. Formatos de data (ISODateTime), códigos de país, estrutura de endereços, tipos de identificadores. Você não precisa mais adivinhar se a data do cliente virá como DD/MM/AAAA ou AAAA-MM-DD. O padrão dita o formato, eliminando uma classe inteira de bugs de parsing.

  2. Estrutura Previsível (O DNA do PIX): Toda a comunicação do Pix com o SPI (Sistema de Pagamentos Instantâneos) acontece via mensagens XML estruturadas sob a ISO 20022. A mensagem de uma ordem de pagamento, por exemplo, é a pacs.008. Dentro dela, há campos rigidamente definidos para cada pedaço da informação: quem paga, quem recebe, o valor exato (IntrBkSttlmAmt), o identificador da transação (EndToEndId), etc. Seu sistema pode não gerar esse XML diretamente, mas o banco ou PSP para o qual você se conecta, com certeza o faz. Uma falha de precisão ou formato em qualquer um desses campos causa a rejeição da transação pelo SPI.

  3. Contratos de Dados Alinhados: Ao construir um sistema que lida com Pix, a melhor prática é alinhar seus próprios modelos de dados (suas classes, structs ou interfaces) com os conceitos da ISO 20022. Se a norma define um EndToEndId, seu objeto Transacao também deve ter um endToEndId. Isso torna o mapeamento entre seu sistema e o mundo exterior muito mais simples, robusto e menos propenso a erros.

A ISO 20022 força os sistemas a concordarem sobre a precisão e o formato dos valores que estão trocando, evitando erros de interpretação entre bancos que rodam em tecnologias, linguagens e até países diferentes. Ela é a garantia de que um valor, calculado com precisão decimal no seu sistema, não será corrompido ou mal interpretado ao viajar pela rede financeira global.

Galera quanta coisa vimos até aqui hein! 😅

Deixe um comentário

A Ponta do Iceberg

Exploramos cinco princípios que formam a espinha dorsal de qualquer sistema financeiro robusto. Cobrimos o básico, o inegociável, o alicerce sobre o qual softwares confiáveis são construídos.

Mas não se engane: esta é apenas a ponta do iceberg. O universo da engenharia de software para finanças é vasto e profundo. Ficaram de fora temas cruciais como estratégias de segurança em múltiplas camadas, o design de processos de conciliação contábil, a complexidade dos sistemas de mensageria e a arquitetura para suportar baixíssima latência em cenários de alta frequência.

Quem sabe em um próximo artigo, não mergulhamos nesses tópicos mais avançados? Por agora, o mais importante é consolidar o que vimos até aqui.

O que nós, como programadores, aprendemos?

Se pudéssemos resumir essa conversa em alguns pontos altos, o que levaríamos para nossa próxima feature, nosso próximo serviço ou nossa próxima startup?

  1. Aprendemos a respeitar o tempo. Vimos que o passado é imutável e não pode ser reescrito. Nosso trabalho é registrar a história financeira, não alterá-la. Entendemos que o presente é caótico e repetitivo, e por isso a idempotência é nosso escudo, garantindo que a instabilidade do mundo real não quebre a lógica do nosso sistema.

  2. Aprendemos a fazer escolhas difíceis. Diante do dilema entre estar sempre disponível e estar sempre correto, entendemos que, para dinheiro, a consistência não é uma opção, é uma obrigação. Aprendemos a travar operações e a fazer o cliente esperar um segundo a mais para garantir que a verdade do sistema seja absoluta.

  3. Aprendemos a ser accountable (responsáveis). Descobrimos que a auditoria não é um luxo ou um pedido chato do time de compliance, mas uma feature essencial. Nossa responsabilidade não termina quando a transação é processada; ela continua na nossa capacidade de provar, de forma irrefutável, o "quê", o "quem", o "quando" e o "porquê" de cada centavo que se moveu.

  4. Aprendemos a matemática de verdade. Deixamos de lado a conveniência perigosa dos floats e doubles e abraçamos a precisão matemática dos tipos decimais. Entendemos que 0.1 + 0.2 precisa ser exatamente 0.3, e que as regras do jogo, como o arredondamento, não são definidas por nós, mas por reguladores como o Banco Central.

No fim das contas, a grande lição é uma mudança de mentalidade. Construir software financeiro é menos sobre criar algoritmos complexos e mais sobre gerenciar risco e construir confiança. Em um aplicativo comum, um bug é uma inconveniência. Em um aplicativo financeiro, um bug é uma crise que pode abalar o bem mais precioso que uma instituição tem: a confiança do seu cliente.

O código que escrevemos não move apenas dados em um banco de dados; ele move o sustento, os sonhos e o futuro financeiro das pessoas. E essa é uma responsabilidade que vai muito além de qualquer framework ou linguagem de programação.

Obrigado por ler Crônicas de um Programador Pragmático! Inscreva-se gratuitamente para receber novas postagens e apoiar meu trabalho.

1

Se você já se perguntou “como o banco faz pra saber em qual fatura vai cair o estorno de uma compra ou o cashback?”, a resposta está em duas palavrinhas que todo programador precisa entender: eventos e ledger.

Na prática, quando um ciclo de fatura é fechado, ele se torna um snapshot imutável. É como tirar aquela foto de família no Natal: ninguém vai poder entrar ou sair depois do clique. O sistema registra todos os débitos daquele período e congela o saldo.

Só que a vida real continua. Você cancela uma compra, recebe um cashback, o IOF de uma compra internacional é estornado… E aí? O banco não reabre a foto antiga. Ele cria novos eventos no ledger.

Pense no ledger como um diário contábil distribuído:

  • Cada transação vira uma linha, com tipo (crédito/débito), data e referência.

  • Quando a fatura do mês passado foi fechada, o ledger marcou todas as transações dela como “consolidadas”.

  • Se um crédito chega depois (cancelamento, cashback, estorno), esse evento é registrado no ledger e mapeado para o ciclo atual, que está em aberto.

Ou seja: o cancelamento nunca “apaga” o lançamento anterior. Ele gera uma nova linha compensatória que vai aparecer como crédito na sua próxima fatura.

A Pismo (e outros processadores modernos) tratam tudo como eventos financeiros imutáveis. Cada compra aprovada gera um evento de débito. Cada estorno, um evento de crédito. A fatura nada mais é do que uma visão agregada do ledger dentro de um intervalo de tempo.

Isso tem algumas vantagens enormes:

  • Auditabilidade: você sempre consegue reconstruir qualquer fatura passada só olhando os eventos daquele período.

  • Idempotência: o sistema não depende de “atualizar linhas”, mas de adicionar novos eventos.

  • Clareza contábil: crédito e débito ficam registrados como fatos distintos, e não como “edições de fatura”.

No fim das contas, é por isso que um estorno nunca mexe na fatura fechada: porque do ponto de vista de sistemas financeiros, não existe “editar” — só adicionar novos eventos ao ledger.


2

Para isso temos Saga que é a alternativa mais famosa e discutida porque representa uma mudança de filosofia fundamental (trocar consistência imediata por eventual), mas existem outras estratégias no nosso cinto de ferramentas, algumas que até podem ser combinadas com Sagas para torná-las mais robustas.

Vamos dar uma olhada no cardápio de opções. 😁

1. Padrão Transactional Outbox (O “Carteiro Confiável”)

Esse padrão não é tanto uma alternativa completa à Saga, mas uma técnica crucial para fazê-la funcionar de forma confiável. Ele resolve um problema específico: “Como eu garanto que, após salvar algo no meu banco de dados, a mensagem para o próximo serviço será realmente enviada, mesmo que a minha aplicação caia no meio do caminho?”.

Como funciona: Em vez de fazer duas coisas separadas (salvar no banco e depois publicar no Kafka/RabbitMQ), você faz uma única operação atômica:

  1. Dentro da mesma transação do banco de dados, você salva a sua entidade de negócio (ex: Pedido) E insere uma linha em uma tabela chamada outbox_events com a mensagem que você quer enviar (ex: um evento PedidoCriado).

  2. Um processo separado e assíncrono monitora essa tabela outbox_events. Quando ele vê uma nova linha, ele a publica no message broker e, só depois de ter certeza que a mensagem foi enviada, ele marca a linha como “processada”.

Trade-off: Você garante o envio da mensagem (”at-least-once delivery”), mas adiciona uma pequena latência, pois o envio não é mais instantâneo. É a escolha pragmática para garantir que os passos da sua Saga (ou qualquer comunicação assíncrona) não se percam no caminho.

2. TCC (Try-Confirm/Cancel)

Este é um pouco próximo do 2PC e da Saga, mas com uma abordagem de “reserva”. Tente pensar nele como reservar uma mesa em um restaurante.

Como funciona em três fases:

  1. Try (Tentar/Reservar): O orquestrador da transação pede a cada serviço para reservar os recursos necessários, mas sem efetivá-los. Ex: o serviço de estoque não diminui o contador do produto, mas cria uma “reserva” para aquele item, tornando-o indisponível para outras transações.

  2. Confirm (Confirmar): Se todas as fases “Try” foram bem-sucedidas, o orquestrador manda uma mensagem de “Confirm” para todos os serviços. Agora sim, eles efetivam a transação (a reserva no estoque vira uma baixa definitiva).

  3. Cancel (Cancelar): Se qualquer fase “Try” falhar, o orquestrador manda uma mensagem de “Cancel” para todos os serviços que já tinham feito a reserva, pedindo para que liberem os recursos.

Diferença da Saga: Na Saga, você efetiva a operação e depois compensa se algo der errado. No TCC, você primeiro reserva tudo e só efetiva no final. Isso significa que os recursos ficam bloqueados por mais tempo, o que pode ser um problema de concorrência, mas evita o estado de “inconsistência temporária” da Saga, o que pode ser mais simples para o negócio entender.

3. Reconciliação Periódica (O “Auditor Noturno”)

Essa abordagem é a mais pragmática e ainda é comum no mundo financeiro. A ideia é: aceite que os sistemas vão ficar inconsistentes em algum momento e, em vez de tentar evitar isso a todo custo em tempo real, crie um processo para detectar e corrigir as discrepâncias.

Como funciona:

  • Os serviços operam de forma mais otimista, talvez com menos garantias transacionais.

  • Periodicamente (toda noite, de hora em hora), um processo de reconciliação (ou “batch”) é executado. Ele lê os dados de diferentes sistemas, compara-os, encontra as divergências e gera as transações de ajuste necessárias para colocar tudo em ordem.

Exemplo clássico: A conciliação de extratos bancários. O banco não trava o mundo a cada transação; ele processa um volume gigantesco de operações e, no final do dia, roda processos pesados para garantir que todas as contas fechem.

Trade-off: É a abordagem com a menor sobrecarga transacional, mas a consistência é a mais “eventual” de todas, podendo levar horas para ser alcançada. É perfeita para cenários que não exigem consistência em tempo real.