Engenharia de Crédito: Implementando Máquinas de Estado
Se você trabalha como engenheiro de software há algum tempo, provavelmente já se deparou com uma tabela no banco de dados que possui uma coluna chamada status. No início, eram apenas dois: PENDENTE e APROVADO. Meses depois, a coluna aceitava CANCELADO, BLOQUEADO, EM_ANALISE, ESTORNADO e uma dezena de outros valores que brotaram conforme o negócio crescia.
O problema real não são os nomes em si, mas o que acontece entre os estados.
Você já viu um contrato CANCELADO receber um evento de PAGAMENTO_CONFIRMADO e, do nada, voltar a ser ATIVO? Ou um cliente inadimplente conseguir realizar um saque porque o motor de decisão emitiu o bloqueio, mas o sistema de destino não sabia se “podia” acatar aquela ordem naquele momento?
Em sistemas financeiros, a consistência não é apenas uma “boa prática” — é uma questão de sobrevivência e conformidade jurídica. Quando falamos de Arquiteturas Orientadas a Eventos (EDA), onde tudo acontece de forma assíncrona e distribuída, confiar que uma simples atualização de coluna no banco manterá a integridade do seu domínio é um erro perigoso.
Neste artigo, vamos mergulhar na origem da necessidade das Máquinas de Estado Finitas (FSM). Vamos sair do básico “banco de dados como espelho de status” e entender como transformar o ciclo de vida de um contrato de crédito — com todas as suas complexidades de bloqueios, liquidações e razões judiciais — em um fluxo determinístico, auditável e, acima de tudo, pragmático.
Se você quer parar de debugar “estados impossíveis” e começar a construir sistemas que se defendem sozinhos, este texto é para você.
Teoria dos Autômatos
Antes de falarmos sobre Kafka, Microsserviços ou Banco de Dados, precisamos dar um passo atrás. O que vamos discutir aqui não é uma “tendência” nova da engenharia de software; é um fundamento que existe muito antes da primeira linha de código web ser escrita.
A solução para a complexidade de estados reside na Teoria dos Autômatos, um ramo da ciência da computação e matemática.1
O conceito de Máquina de Estados Finitos (FSM) foi formalizado nas décadas de 1940 e 1950. Pioneiros como George H. Mealy e Edward F. Moore desenvolveram modelos matemáticos para descrever o comportamento de circuitos lógicos sequenciais. Eles precisavam de uma forma de garantir que, dada uma entrada X, o sistema reagisse de forma previsível Y.
O Conceito Matemático (Simplificado)
Formalmente, uma Máquina de Estados Finita Determinística é definida por uma 5-tupla (Q, Σ, Δ, Q0, F). Não se assuste com os símbolos, eles são exatamente o que você programa no dia a dia:
Q: Um conjunto finito de Estados (ex:
SIGNED,BLOCKED).Σ: Um alfabeto finito de entrada, que chamaremos de Gatilhos/Eventos (ex:
PAGAMENTO_RECEBIDO).Δ: A Função de Transição. É a regra que diz: Δ(estado_atual, evento) → proximo_estado.
q_0: O Estado Inicial (onde tudo começa, ex:
PRE_ANALYSIS).F: O conjunto de Estados Finais (onde o processo termina, ex:
CLOSED).
Entender isso é vital porque transforma “regras de negócio soltas” em uma equaçao matemática que não permite ambiguidade.
A Catraca do Metrô
Para tirar essa matemática do papel e torná-la tangível, esqueça o código e pense em uma catraca de metrô (ou torniquete). Ela é a implementação física perfeita dessa tupla matemática.
A catraca possui apenas dois estados (Q):
Bloqueada (Locked)
Desbloqueada (Unlocked)
E aceita duas ações ou inputs do mundo externo (Σ):
Inserir Moeda
Empurrar a Barra
A “magia” — e a segurança do sistema — não está nos estados, mas na Função de Transição (Δ):
Se você Empurrar enquanto está Bloqueada, a função retorna o próprio estado Bloqueada. Nada acontece.
Se você Inserir Moeda enquanto está Bloqueada, a função muda o estado para Desbloqueada.
Se você Empurrar enquanto está Desbloqueada, você passa, e a máquina transita automaticamente de volta para Bloqueada.
Se a catraca fosse implementada como a maioria dos nossos sistemas legados (o modelo de “banco de dados espelho”), ela não teria regras. Seria possível forçar a ação “Passar” sem inserir a moeda, bastando um desenvolvedor rodar um update manual: UPDATE catraca SET travada = false.
Na vida real, a barreira física da catraca impede essa fraude. No software, a FSM é essa barreira física.
Adotar FSMs em sistemas de crédito marca uma mudança profunda na postura do engenheiro:
Mentalidade CRUD: “Quais dados eu preciso salvar agora?” (Foco na persistência passiva).
Mentalidade FSM: “Dado que o contrato está no estado Q, o evento Σ pertence ao alfabeto aceito? Se sim, qual é a transição Δ obrigatória?” (Foco no comportamento e integridade).
Ao adotar essa postura, saímos do modo reativo (corrigir dados corrompidos no banco porque alguém fez o que não devia) para o modo preventivo (a arquitetura torna matematicamente impossível o erro acontecer).
Por que precisamos de FSM em Sistemas de Crédito?
Se você está construindo um aplicativo de lista de tarefas e um bug permite que o usuário marque uma tarefa como “concluída” duas vezes, o pior que acontece é uma falha visual. É chato, mas inofensivo.
No mundo financeiro, entretanto, a tolerância ao erro é zero. Em sistemas de crédito, um erro de transição de estado é sinônimo de dinheiro perdido ou processo judicial.
Antes de escrevermos a primeira linha de código, precisamos entender o risco. O banco de dados não pode ser apenas um depósito de informações; a arquitetura precisa atuar como um Escudo de Integridade. É aqui que a Máquina de Estados (FSM) deixa de ser um luxo e vira uma obrigação.
O Caos das Colunas de Status e o Banco de Dados como “Espelho”
No início de qualquer projeto, a simplicidade é sedutora. Você modela a entidade CreditContract, cria uma tabela e adiciona uma coluna inofensiva: status (seja uma string ou um enum).
A regra de negócio parece clara: “Quando o cliente assinar, mude o status para SIGNED“. No código, isso se traduz em um simples update:
UPDATE contracts SET status = 'SIGNED' WHERE id = 123;O problema é que, nessa abordagem, o seu banco de dados atua como um espelho passivo. Ele reflete cegamente qualquer coisa que a aplicação ordene. Ele não questiona. Se um bug, uma race condition ou um estagiário rodando um script manual tentar mudar um contrato de CANCELLED (Cancelado) para SIGNED (Assinado), o banco aceitará silenciosamente.
Parabéns, você acabou de criar um estado impossível. Um contrato que não existe mais acabou de “renascer”, gerando inconsistência contábil e potencial fraude. A dor aqui não é apenas técnica; é a perda de confiança na integridade dos dados.
O Custo do “Estado Impossível”
Imagine um contrato de crédito que foi quitado pelo cliente. O status atual é CLOSED (Encerrado). O cliente está feliz e com o “nome limpo”.
Porém, em uma arquitetura de microsserviços distribuída, uma mensagem antiga de atraso — que ficou presa em uma fila de retentativa (DLQ) por 5 dias — finalmente é processada. Esse evento diz: “Bloqueie este contrato por inadimplência”.
Sem uma FSM:
O sistema recebe o evento.
Atualiza o status de
CLOSEDparaBLOCKED.Dispara gatilhos que negativam o nome do cliente nos órgãos de proteção ao crédito.
Resultado: Um processo judicial por danos morais contra a sua empresa, causado por um cliente que já havia pago tudo.
Com uma FSM:
O sistema recebe o evento de bloqueio.
A FSM consulta a configuração: “Existe uma transição válida de
CLOSEDparaBLOCKED?”A resposta é NÃO. Um contrato encerrado é imutável em relação a bloqueios.
A FSM rejeita a transição, loga o erro e descarta a mensagem silenciosamente ou alerta o time de sustentação. O cliente permanece protegido.
É bom lembrar que a fraude em sistemas de crédito nem sempre vem de ataques externos; muitas vezes ela explora brechas na lógica de fluxo.
Em um fluxo de originação de crédito, a ordem das etapas é sagrada: PRE_ANALYSIS (Score/KYC) → PENDING_SIGNED (Aguardando Assinatura) → SIGNED (Efetivado).
Um fraudador (ou um bug crítico) poderia tentar chamar diretamente a API de efetivação (/confirm-contract), tentando pular a etapa de análise de risco (PRE_ANALYSIS), forçando o contrato a nascer já no estado SIGNED.
Se o seu sistema apenas “salva o status” que chega na requisição, você acabou de conceder crédito sem análise. A FSM atua como um porteiro implacável: ela garante que o estado SIGNED só pode ser alcançado se o estado anterior for estritamente PENDING_SIGNED. Tentar pular etapas resulta em uma exceção imediata.
“Em produção, surpresa é o inimigo”
Sistemas financeiros precisam ser determinísticos. A FSM força o time de engenharia e produto a mapear exatamente o que pode acontecer.
Pode cancelar um contrato que já está bloqueado judicialmente?
Pode renegociar uma dívida que ainda não foi assinada?
Ao definir essas regras na FSM, você remove a ambiguidade. Você tira a lógica de negócio de ifs espalhados por 10 classes diferentes e a centraliza em uma definição formal. Isso evita que, em produção, um cenário não testado coloque o sistema em um estado inconsistente que exija intervenção manual no banco de dados (o famoso “update na base” que todo DBA odeia).
Anatomia de uma Máquina de Estados (E o que ela NÃO é)
Agora que entendemos a motivação, vamos descer para o nível de engenharia. O que compõe, tecnicamente, uma Máquina de Estados? Não estamos falando de bibliotecas específicas, mas dos quatro elementos universais que formam a espinha dorsal desse padrão.
Os 4 Elementos Fundamentais
Estado (State): É uma fotografia do momento. Representa a situação qualitativa do Contrato de Crédito naquele instante exato.
Exemplo:
SIGNED(Assinado/Vigente). Enquanto o contrato está neste estado, o sistema entende que ele é saudável. O estado persiste até que um fato novo ocorra.
Gatilho (Trigger): É o estímulo externo ou a intenção de mudança. É o “fato” que ocorreu no mundo real (ou no sistema) e que bate à porta da máquina.
Exemplo:
INADIMPLENCIA_DETECTADA. Note que o gatilho não é a mudança em si, mas a notificação de que algo aconteceu.
Transição (Transition): É a regra atômica de movimento. É a ponte que conecta dois estados, governada por uma lógica determinística: Se estou no Estado A e recebo o Gatilho X, vou para o Estado B.
Exemplo: De
SIGNED→ para →BLOCKED.
Ação (Action): É o efeito colateral. É a tarefa que o sistema deve agendar ou registrar durante a mudança.
Exemplo: Ao entrar em
BLOCKED, a ação é gerar e persistir o evento de domínioContractBlocked(que notificará outros sistemas).
O Motor de Decisão vs. A Máquina de Estados
Aqui reside a maior confusão arquitetural em sistemas de crédito. É fundamental distinguir quem pensa de quem garante.
Motor de Decisão (Rule Engine): É o Estrategista. Ele analisa variáveis complexas (saldo, dias de atraso, score de crédito, taxa Selic) e emite uma Intenção (Comando): “Este cliente deve ser bloqueado”.
Máquina de Estados (FSM): É o Guardião do Fluxo. Ela não recalcula o risco do cliente. Ela recebe a intenção e valida a integridade: “Dado que o contrato está no estado atual, é tecnicamente possível e permitido transitar para BLOQUEADO?”.
Por que precisamos dessa separação?
Se o Motor de Decisão já “decidiu”, por que a FSM precisa validar novamente? Porque a FSM enxerga riscos de consistência que o Motor ignora:
Integridade do Ciclo de Vida (Imutabilidade): O Motor pode estar analisando dados de segundos atrás. Se, nesse meio tempo, o cliente cancelou a conta, o contrato está
CANCELLED. A FSM impede que um contrato cancelado vire bloqueado. Ela garante que não se altere o status de um processo já encerrado.Concorrência (Race Conditions): Em alta escala, comandos chegam simultaneamente. Se uma ordem judicial de bloqueio chegar milissegundos antes de um bloqueio por atraso, a FSM arbitra a colisão, garantindo que o estado final respeite a prioridade correta e não seja sobrescrito indevidamente.
Prevenção de Atalhos (Bypass): A FSM assegura o rito processual. Se um erro de integração tentar pular de
PRE_ANALYSISdireto paraSIGNED, a FSM barra o movimento porque a etapa obrigatória de assinatura foi ignorada.
A Diferença Fundamental: O Motor de Decisão determina que o contrato deve ser bloqueado (baseado em política). A Máquina de Estados determina se o contrato pode ser bloqueado (baseado em estrutura e ciclo de vida).
Na Prática: Responsabilidades Limpas
Para manter a arquitetura sã, evite colocar lógica de negócio dentro da FSM:
Errado (na FSM):
if (diasAtraso > 10 && divida > 1000) ...Certo (na FSM): Receber o gatilho
INADIMPLENCIA_CRITICA(já processado pelo motor) e apenas validar a transição.
O Motor é Estratégico (muda com as regras de negócio). A Máquina é Operacional (garante a consistência do banco de dados).
Agora que estabelecemos as fronteiras, vamos dissecar cada um desses elementos, começando pelo mais incompreendido: o Estado.
Dissecando os Elementos: STATE (Estado)
Quando a equipe de produto chega com um requisito dizendo: “O contrato precisa ter um status de ‘Em Análise de Risco’”, o instinto do desenvolvedor é abrir o arquivo de Enums e adicionar IN_RISK_ANALYSIS. Pare agora mesmo.
Antes de definir um Estado, o engenheiro precisa fazer uma pergunta fundamental ao negócio:
“O contrato se comporta de maneira diferente neste momento?”
Se a resposta for “Não, é só para a gente saber que está lá”, então isso provavelmente não é um estado de uma FSM, é apenas uma tag, um log ou um atributo informativo.
Um Estado representa um modo de comportamento.
No estado
SIGNED(Assinado), o contrato aceita pagamentos.No estado
BLOCKED(Bloqueado), o contrato rejeita pagamentos.Se o comportamento mudou, o estado mudou.
Onde a Máquina de Estados “Mora”?
Muitos arquitetos imaginam que, para ter uma Máquina de Estados, precisam subir um cluster novo, criar uma “State API” ou configurar algo complexo na AWS. Vamos desmistificar isso.
A FSM não é necessariamente um microsserviço isolado. Ela é, na maioria das vezes, uma propriedade comportamental da sua Entidade de Domínio.
Existem duas formas principais de implementar, e o Tech Lead precisa saber escolher a ferramenta certa para o problema certo:
Cenário A: O Estado “No Código” (Bibliotecas / In-Memory)
Este é o padrão ouro para Entidades de Domínio (como o nosso Contrato de Crédito). A FSM é uma biblioteca que roda dentro do seu microsserviço.
Onde fica o dado? Em uma coluna simples
status(string/enum) na tabela do seu banco de dados (Postgres, DynamoDB, Mongo).Quem gerencia? O código da sua API, utilizando bibliotecas leves como Stateless (.NET), XState (Node.js) ou Spring State Machine (Java).
Como funciona? A FSM é “Stateless” (sem memória). Quando a API recebe um request:
Busca o contrato no banco (ex:
status: 'SIGNED').Instancia a FSM na memória configurada com esse estado inicial.
Tenta disparar o gatilho.
Se a FSM permitir, atualiza o objeto e salva no banco.
A FSM morre da memória ao fim do request.
Para quem é? Para gerenciar o ciclo de vida de entidades que precisam de resposta rápida (milissegundos) e alta integridade transacional.
Cenário B: O Estado “Na Infraestrutura” (Workflow Engines)
Aqui, o Estado é gerenciado por um serviço de nuvem, como AWS Step Functions ou Temporal.io.
Onde fica o dado? O próprio serviço de nuvem mantém o “ponteiro” de em qual etapa o processo está.
Quem gerencia? A plataforma de Cloud. Você desenha o fluxo visualmente.
Para quem é? Para Processos de Longa Duração (Sagas).
Exemplo: Um fluxo de Cobrança que envia um e-mail, espera 3 dias, verifica o pagamento e, se não pagar, envia SMS. Isso não é o estado do contrato, é o estado de um processo que atua sobre o contrato.
O “State” é uma Lambda?
Não. Confundir computação com estado é um erro primário.
O Estado é passivo: “O contrato está
AGUARDANDO_ANALISE“.A Lambda é o trabalhador (Worker): Ela executa a análise.
A FSM é o maestro que diz: “Já que estou no estado ‘AGUARDANDO_ANALISE’ e chegou o evento ‘INICIAR’, vou invocar a Lambda X para trabalhar”.
Responsabilidades: Evitando a Explosão de Estados
Para fechar o conceito, precisamos definir o que o Estado NÃO deve fazer.
O Estado não guarda contexto: O estado é
BLOCKED. O motivo do bloqueio, o valor da dívida ou a data são atributos do contrato, não do estado.Errado: Criar estados como
BLOCKED_POR_FALTA_PAGAMENTO,BLOCKED_JUDICIAL,BLOCKED_FRAUDE. Isso gera uma explosão combinatória impossível de manter.Certo: Tenha um estado
BLOCKEDe uma coluna auxiliarreason_codeoublock_type.
O Estado não realiza lógica complexa: Ele não sabe “como” calcular juros compostos. Ele apenas sabe que, enquanto estiver no estado
BILLING, o cálculo de juros é permitido.
Encare a FSM do Cenário A como uma “camada de proteção lógica” ao redor da sua classe de Domínio. Ela não é um servidor, é um guarda-costas que vive no seu código e protege seu banco de dados de escritas inválidas.
O Gatilho (Trigger)
Se o Estado é uma fotografia estática do seu contrato em um momento no tempo, o Gatilho (ou Trigger) é o movimento. Ele é a energia cinética. Sem gatilhos, sua máquina de estados é apenas um diagrama bonito desenhado no Gliffy ou no Miro, mas completamente inútil na prática.
Para muitos desenvolvedores, o gatilho parece ser a parte mais simples: “Ah, é só o nome do método que eu chamo”. Mas é aqui que a arquitetura começa a se provar robusta ou frágil. Vamos dissecar o que compõe um gatilho de verdade.
O Que é?
Formalmente, o Gatilho é um sinal. É uma solicitação externa que diz à máquina: “Ei, algo aconteceu (ou queremos que aconteça). Verifique se você pode mudar de estado por causa disso.”2
No nosso sistema de crédito, o contrato não acorda de manhã e decide virar BLOCKED sozinho. Algo precisa empurrá-lo. Esse empurrão é o gatilho.
Comando (Intenção) vs. Evento (Fato)
Em Arquiteturas Orientadas a Eventos (EDA), a semântica do gatilho muda o jogo. Precisamos separar duas categorias:
1. O Gatilho como Comando (Intenção): É imperativo. Ocorre quando um usuário ou sistema quer que algo mude.
Exemplo: O usuário clica no botão “Renegociar Dívida” no app.
Gatilho:
SolicitarRenegociacao.Natureza: A FSM tem todo o direito de rejeitar. Se o contrato estiver
BLOCKED_JUDICIALLY(Bloqueio Judicial), a máquina diz: “Não. Rejeito seu comando”.
2. O Gatilho como Evento (Fato): É pretérito. Ocorre quando algo já aconteceu e a FSM precisa reagir para manter a consistência.
Exemplo: O gateway de pagamento enviou um webhook confirmando que o dinheiro caiu na conta.
Gatilho:
PagamentoConfirmado.Natureza: A FSM não pode “negar” que o pagamento ocorreu. O dinheiro já saiu da conta do cliente. A FSM deve aceitar o gatilho e transitar para
PAIDou, se estiver num estado onde isso é impossível (comoCANCELLED), deve iniciar um processo de reembolso ou logar uma inconsistência grave.
O Gatilho carrega “Bagagem” (Payload)
Um erro clássico de iniciantes é tratar o Gatilho como uma simples flag booleana (ex: “pagou” ou “não pagou”). Para a FSM do Contrato de Crédito, o pagamento de uma fatura é um fato externo que traz dados cruciais.
Um gatilho robusto deve transportar o Contexto (Payload) necessário para que a transição do Contrato ocorra de forma isolada, sem que a FSM precise “adivinhar” o que aconteceu no módulo de faturamento.
Reagindo à Fatura
Imagine que o seu contrato está no estado BLOCKED (Bloqueado) porque havia uma pendência. O cliente paga uma fatura. Para a FSM decidir se o Contrato volta a ser SIGNED (Ativo/Saudável), ela precisa dos dados do ciclo:
Gatilho:
PAGAMENTO_FATURA_RECEBIDOPayload:
{
"fatura_id": "ciclo_jan_2026",
"valor_pago": 150.00,
"valor_total_fatura": 150.00,
"status_final_fatura": "TOTAL_PAID"
}
Por que isso é vital para a Arquitetura?
A Máquina de Estados utiliza esse payload para alimentar as Guards (Guardas). Com o contexto na mão, a lógica de roteamento do contrato fica protegida:
Regra: “Se o contrato está
BLOCKEDe o gatilho trazstatus_final_fatura == TOTAL_PAID, transite o contrato paraSIGNED.”Regra: “Se o gatilho traz
status_final_fatura == PARTIAL_PAID, mantenha o contrato emBILLING(Em Cobrança).”3
O Benefício: FSM “Stateless” e de Alta Performance
O objetivo fundamental da FSM não é gerenciar o saldo da fatura — isso é função do módulo de faturamento. A missão da FSM é validar se o Contrato de Crédito pode mudar de estágio.
Eliminamos Queries Pesadas: A FSM não precisa abrir conexões com a API de faturamento para perguntar “o saldo dessa fatura está zerado?”. Ela recebe essa informação como um fato no payload.
Reduzimos o Acoplamento: A FSM do contrato não conhece as regras de cálculo de juros da fatura. Ela apenas reage ao resultado final enviado pelo gatilho.
Garantimos Determinismo: O estado do contrato muda com base em fatos imutáveis entregues no momento do disparo.
O Tempo como Gatilho
Em sistemas de crédito, o tempo é o gatilho mais implacável. Muitas mudanças no contrato não vêm de uma ação do cliente, mas do relógio.
Cenário: O contrato está
SIGNED. A fatura venceu ontem e não houve pagamento.Quem dispara? Um componente externo de Scheduler (AWS EventBridge, Hangfire ou um Cron Job).
A Arquitetura: O Scheduler identifica a fatura vencida e “empurra” o gatilho
FATURA_VENCIDA_NAO_PAGAcontra a FSM do Contrato.
A FSM recebe o sinal, verifica que o contrato está em um estado que permite cobrança e transita o contrato automaticamente para BILLING ou BLOCKED. Para a FSM, o tempo é apenas mais um ator externo enviando sinais.
Lidando com Gatilhos “Fora de Hora”
O que acontece se a FSM receber um sinal que não faz sentido para o estado atual do Contrato? (Ex: Receber um aviso de pagamento para um contrato que já foi CANCELLED).
Existem duas abordagens de design para garantir a resiliência:
Abordagem Estrita (Throw Exception): Útil para capturar erros de lógica em desenvolvimento. Se tentarmos disparar
EFETUAR_SAQUEem um contratoBLOCKED, o sistema lança umInvalidTransitionException.Abordagem Resiliente (Idempotência/Ignore): Essencial em sistemas distribuídos (Kafka/RabbitMQ). Se um evento de pagamento for processado duas vezes (replay), a FSM olha para o contrato, vê que ele já está
SIGNEDe simplesmente ignora o segundo gatilho de forma silenciosa e segura.
O Gatilho é a única API da sua Máquina de Estados. Nunca altere o estado do contrato diretamente no banco de dados.
Errado: update contrato set status = 'CANCELLED'
Certo: contratoFsm.Fire(Triggers.SOLICITAR_CANCELAMENTO)
Ao forçar todas as mudanças através de Gatilhos e Payloads, você garante que o ciclo de vida do crédito seja auditável, previsível e, acima de tudo, protegido contra estados impossíveis.
A Transição (Transition)
A Transição é o componente que define as regras de movimentação entre estados. Em uma Máquina de Estados (FSM), ela é muito mais do que uma seta em um diagrama; é a camada de aplicação das regras de negócio.
Enquanto o Estado representa a situação qualitativa do Contrato (ex: SIGNED) e o Gatilho representa a intenção de mudança (ex: PAGAMENTO_RECEBIDO), a Transição é a lógica que governa se — e como — essa mudança pode ocorrer. Ela é fundamental porque centraliza o controle de fluxo, garantindo que o Contrato nunca assuma um comportamento indefinido.
Vamos dissecar os quatro mecanismos que tornam a transição uma ferramenta poderosa de arquitetura:
1. Permissão Explícita (Whitelisting)
Diferente de códigos procedurais onde tudo é permitido até que se escreva uma validação para proibir, a FSM opera pelo princípio da Permissão Explícita. Se uma transição não estiver mapeada, ela simplesmente não existe.
Cenário: Um contrato está no estado
CANCELLED(Cancelado definitivamente). O sistema recebe o gatilhoSOLICITAR_AUMENTO_LIMITE.Comportamento: A FSM consulta a definição do estado
CANCELLED. Como não há nenhuma seta de saída configurada para esse gatilho a partir deste estado, a solicitação é rejeitada automaticamente.
A própria arquitetura garante a integridade do contrato, impedindo operações inválidas sem a necessidade de espalhar if (status != CANCELLED) por todo o código.
2. Cláusulas de Guarda (Guards): Roteamento Condicional
Uma transição nem sempre é linear. Frequentemente, o destino do Contrato depende dos dados contidos no payload do gatilho (como o status da fatura). Para gerenciar isso, utilizamos Guards — expressões booleanas avaliadas antes da transição ocorrer.
Imagine um contrato que está no estado BLOCKED (Bloqueado por inadimplência). O sistema recebe o gatilho PROCESSAMENTO_FATURA_CONCLUIDO. A FSM precisa decidir se desbloqueia o contrato ou se ele permanece bloqueado.
A transição analisa o payload da fatura:
Guard A:
Se payload.status_fatura == "TOTALLY_PAID"→ O Contrato transita paraSIGNED(Desbloqueia e volta a ficar saudável).Guard B:
Se payload.status_fatura == "PARTIALLY_PAID"→ O Contrato transita (ou permanece via auto-transição) emBLOCKED(Pois a pendência não foi totalmente sanada).
Note que PARTIALLY_PAID é o status da fatura (input), mas o estado do contrato (output) permanece BLOCKED. A Guard usa o dado da fatura para decidir o destino do contrato.
3. Irreversibilidade e Auditoria (Forward Recovery)
Em sistemas orientados a eventos, as transições geram efeitos colaterais. Quando um contrato transita para BLOCKED, eventos são disparados para suspender o cartão na processadora e notificar o cliente. Por isso, não podemos simplesmente “desfazer” uma mudança voltando o estado no banco de dados.
Se um contrato foi bloqueado indevidamente, reverter o estado manualmente corrompe a realidade, pois o cartão já foi bloqueado na ponta. A abordagem correta é o Forward Recovery (Recuperação para Frente): cria-se uma transição corretiva (ex: DESBLOQUEIO_ADMINISTRATIVO) que move o contrato de BLOCKED para SIGNED. Isso preserva a trilha de auditoria: o histórico mostrará que o contrato foi bloqueado e, segundos depois, desbloqueado administrativamente.
4. Auto-Transição (Self-Transition): Reatividade sem Mudança
Existem cenários onde o Contrato deve reagir a um evento, mas seu estado qualitativo não muda.
Cenário: O contrato está em
BLOCKED. Ocorre uma nova tentativa de pagamento que falha. O gatilhoFALHA_PAGAMENTOé disparado.Ação: O contrato permanece em
BLOCKED(a transição aponta para o próprio estado).
Apesar de o estado não mudar, a transição dispara uma Ação (Action): “Registrar a falha no histórico de cobrança” ou “Enviar Push Notification de falha”. A Auto-Transição permite processar regras de negócio mantendo a estabilidade do estado atual do contrato.
Pausa para Esclarecer: Transição ≠ Motor de Decisão ≠ Ação
Se você leu o tópico anterior e pensou:
“Espere, isso parece muito com um Motor de Regras (Rule Engine)” ou
“Parece que a própria transição faz o trabalho” — você não está sozinho.
Essa é exatamente a fronteira onde uma boa arquitetura começa a ficar confusa.
Para não transformarmos nossa Máquina de Estados em um “Deus Ex Machina” que decide, executa e orquestra tudo, precisamos traçar duas linhas vermelhas muito claras no chão.
1. Transição não é Motor de Decisão (Decision Engine)
Existe uma diferença vital de responsabilidade, complexidade e momento de execução.
Motor de Decisão (o “Cérebro”)
É quem faz o trabalho pesado, consulta bureaus de crédito (Serasa/SPC), calcula scores de risco, aplica modelos estatísticos e decide se um cliente merece ou não crédito. Ele é focado no “POR QUÊ”.
Transição da FSM (o “Guarda de Trânsito”)
É focada na ESTRUTURA do fluxo.
Ela não calcula score.
Ela apenas valida algo como:
“O score que chegou no payload é maior que 500?
Se sim, essa transição é permitida.”
A Transição não decide o mundo.
Ela apenas valida se o mundo já decidido permite o próximo passo.
Regra de Ouro:
Se a lógica demora mais do que alguns milissegundos, envolve cálculos complexos ou precisa chamar APIs externas, ela não pertence à Transição (Guard).
Esse trabalho deve ser feito antes, por um serviço dedicado, e o resultado passado para a FSM apenas para validar o roteamento.
2. Transição não é Ação
A Transição é apenas a autorização de movimento.
Ela é o carimbo no passaporte.
Mas carimbar o passaporte não faz o avião decolar.
A Transição diz apenas:
“Ok. O contrato saiu de SIGNED e agora está BLOCKED.”
Mas isso levanta perguntas importantes:
Quem bloqueia o cartão de crédito?
Quem envia o e-mail de “Sinto muito”?
Quem torna esse fato observável para o resto do sistema?
A Transição é o Mapa.
A Ação é o registro das consequências da viagem.
Se a Transição responde apenas “posso ir ou não?”, então onde ficam os efeitos que realmente impactam o mundo externo?
É exatamente aqui que entram as Actions (Ações).
Actions (Ações): O Dilema do “Falar vs. Escrever”
Chegamos ao último elemento fundamental da Máquina de Estados. Se a Transição é a burocracia que autoriza a mudança, a Action é a materialização durável dessa decisão.
É o momento em que a FSM deixa de apenas avaliar e passa a registrar as consequências.
Quando um contrato transita de SIGNED para BLOCKED, a FSM não deve sair “falando” com outros serviços. Ela não bloqueia cartões diretamente, não envia e-mails e não chama o Kafka. Mas ela precisa tornar esse fato observável.
A pergunta que separa desenvolvedores juniores de seniores não é “o que fazer”, mas sim:
“Como tornar essa decisão observável sem quebrar a consistência dos dados?”
O Jeito Ingênuo (O Perigo do Dual Write)
O instinto inicial é escrever um código sequencial que parece inofensivo:
Atualiza o banco:
UPDATE contrato SET status = 'BLOCKED'Publica no Kafka:
producer.send(ContractBlockedEvent)
O problema é simples e cruel: redes falham. Se o passo 1 funcionar (o banco commitar), mas o passo 2 falhar (timeout no Kafka, erro de DNS), você criou uma catástrofe silenciosa:
No Banco: O cliente está bloqueado.
No Mundo Real: O sistema de cartões nunca ficou sabendo.
Resultado: O cliente continua gastando, gerando prejuízo financeiro.
Esse cenário é o clássico Dual Write Problem: tentar escrever em dois sistemas de armazenamento diferentes (Banco e Broker) sem uma transação que una os dois.
Onde Está o Erro?
Aqui mora o perigo. Ao colocar uma chamada de rede (Kafka, HTTP, gRPC) dentro do fluxo da FSM — especialmente dentro de uma transação de banco — você está acoplando a integridade dos seus dados à instabilidade da rede externa.
Se o banco salva, mas a rede falha, a FSM acredita que o trabalho foi feito. Mas a notificação se perdeu.
Transactional Outbox
Uma FSM madura adota uma postura defensiva. Ela não tenta entregar mensagens; ela apenas registra decisões.
A FSM não deve ser o “motoboy” que entrega a carta. Ela deve ser o “Escriturário” que carimba e arquiva o documento oficial. O fluxo robusto é:
A FSM decide a mudança de estado.
A FSM monta o evento (o payload JSON).
A FSM salva o Novo Status E o Evento na tabela Outbox na mesma transação de banco de dados (ACID).
Por estarem na mesma transação local, ou os dois são salvos, ou nenhum é. É fisicamente impossível o contrato mudar de estado sem que o evento correspondente seja gravado.
Quem Fala com o Mundo?
A FSM encerra sua responsabilidade ao escrever a verdade no banco. A entrega do evento ao Kafka é delegada a um processo separado — um Worker, Relay ou ferramenta de CDC (Change Data Capture).
Esse componente “Carteiro”:
Lê a tabela
OUTBOX_EVENTS.Recupera os eventos pendentes.
Publica no Kafka.
Se o Kafka estiver fora do ar, ele espera e tenta novamente (Retry).
Resultado:
Se o Kafka cair, o evento continua seguro no banco.
Nenhuma informação se perde.
A entrega acontece assim que a infraestrutura voltar.
Ao separar claramente a Decisão (Motor), o Roteamento (Transições) e o Registro (Action + Outbox), a Máquina de Estados deixa de ser frágil. Ela não executa o mundo; ela declara a verdade do domínio, garantindo que o estado do contrato e os eventos observados pelos outros sistemas estejam sempre em eventual, porém garantida, sintonia.
O “Porquê” Importa: Status vs. Status Reason (Motivo)
Agora que a nossa Máquina de Estados (FSM) está sólida, vamos enfrentar um problema de design que apanha 9 em cada 10 equipas de engenharia: a Explosão de Estados.
Imagine que está a desenhar o estado de Cancelamento. O negócio diz-lhe:
“Precisamos de saber se o contrato foi cancelado pelo cliente, por fraude…”
O instinto inicial do programador é criar três estados:
CANCELLED_BY_CUSTOMERCANCELLED_BY_FRAUDCANCELLED_BY_PARTNER
Não faça isto. Se seguir este caminho, a sua FSM vai transformar-se numa teia de aranha impossível de manter. Para cada um destes três estados, teria de duplicar as transições, as actions e os guards.
A Solução: Dividir para Conquistar
Para manter a sanidade da arquitetura — e da equipe — precisamos separar duas perguntas que parecem semelhantes, mas não são:
Onde estou? → Isso define o comportamento do sistema
Por que estou aqui? → Isso define o contexto de negócio
Misturar essas duas coisas é o caminho mais curto para uma FSM ilegível.
O Status Define o Comportamento
O Status deve representar apenas o ciclo de vida macroscópico do contrato.
Ele responde a uma pergunta simples:
“O que este contrato pode ou não fazer agora?”
Se um contrato está em CANCELLED, o comportamento é o mesmo, independentemente do motivo:
Não gera juros
Não aceita pagamentos
Não envia faturas
Do ponto de vista do sistema, não importa por que ele foi cancelado.
O comportamento é único.
Por isso, tenha apenas um estado CANCELLED.
Criar CANCELLED_BY_CLIENT, CANCELLED_BY_FRAUD, CANCELLED_BY_BANK não adiciona comportamento — apenas confusão.
O Reason Define a Regra de Negócio (a Nuance)
O Status Reason é um campo satélite.
Ele não muda o comportamento base do estado, mas explica a causa raiz que levou até ele.
É aqui que a granularidade de negócio vive.
Esse campo é usado para:
autorizar ou bloquear transições futuras
aplicar regras específicas
justificar decisões regulatórias ou jurídicas
Ele não cria novos estados, ele informa decisões.
Exemplo Prático: Estado BLOCKED
Vamos olhar para o estado BLOCKED.
O comportamento é claro:
crédito suspenso
operações bloqueadas
risco controlado
Mas o motivo do bloqueio importa — e muito.
Cenário A
Status:
BLOCKEDReason:
LACK_OF_PAYMENT
Implicação:
Se chegar um evento PAYMENT_RECEIVED, a FSM pode desbloquear automaticamente e retornar para SIGNED.
Cenário B
Status:
BLOCKEDReason:
JUDICIAL_ORDER
Implicação: Se chegar o mesmo evento PAYMENT_RECEIVED, a FSM não pode desbloquear.
O dinheiro entra. A dívida diminui. Mas o contrato continua bloqueado até que um comando jurídico explícito seja recebido.
Por que isso é melhor do que múltiplos status?
Porque você mantém:
Poucos estados
Comportamentos claros
FSM legível
Regras concentradas
Evolução sem explosão de complexidade
O Status responde “o que o sistema faz”.
O Reason responde “sob quais condições ele pode mudar”.
Separar essas responsabilidades não é confusão — é design consciente.
FSM + Event-Driven: A Arquitetura da Consistência
Agora que definimos a mecânica interna, precisamos olhar para fora. Como a FSM se comporta como o coração de uma arquitetura distribuída?
A FSM como a Fonte Única da Verdade (SSOT)
Em microsserviços, o maior risco arquitetural é o Split Brain (Cérebro Dividido): o Serviço de Cobrança acredita que o contrato está ativo, enquanto o Serviço de Risco acredita que ele está bloqueado. Isso ocorre quando a mudança de estado e a notificação dessa mudança não são atômicas.
A FSM resolve isso centralizando a autoridade de escrita. Ela opera sob o princípio de Single Writer: apenas a máquina de estados tem permissão para alterar a coluna status do contrato. O fluxo de confiança é garantido pela transacionalidade:
Validação: O gatilho entra, as Guards validam e a Transição é aprovada.
Atomicidade (O Pulo do Gato): Na mesma transação de banco de dados (ACID), a FSM realiza duas operações:
Atualiza o estado do contrato (
UPDATE contracts SET status...).Insere o evento na tabela de saída (
INSERT INTO outbox...).
Garantia: É matematicamente impossível o estado mudar sem que o evento seja gerado. O evento
ContractBlockedEventnão é um “aviso”; é um fato imutável que reflete a realidade persistida.
Orquestração Local, Coreografia Global
Arquitetos frequentemente debatem entre Coreografia (serviços reagem a eventos soltos) e Orquestração (um maestro comanda tudo). A FSM introduz um padrão híbrido eficiente: Orquestração Intra-Serviço e Coreografia Inter-Serviços.
Internamente (Orquestração): A FSM é tirana. Ela orquestra rigidamente o ciclo de vida dentro do Bounded Context de Crédito. Ela não permite que etapas sejam puladas. Ela garante a consistência imediata do domínio.
Externamente (Coreografia): Uma vez que a transição ocorre e o evento é publicado, a FSM não se importa com quem vai consumi-lo. O Serviço de Marketing pode consumir o evento para enviar um e-mail, e o Jurídico para abrir um processo.
Isso desacopla os domínios. A FSM garante que o sinal emitido é puro e confiável, permitindo que os consumidores reajam livremente sem acoplar a lógica de negócio central.
Auditabilidade e Causalidade (Forensics)
Logs de aplicação (como no Datadog ou Splunk) mostram que um erro ocorreu. A Máquina de Estados mostra por que o sistema chegou naquele estado.
Em sistemas financeiros auditados, saber o estado atual não basta. Precisamos da Causalidade. Ao persistir o histórico de transições (state_transitions table), criamos uma trilha forense:
Estado Anterior:
SIGNEDGatilho:
PAGAMENTO_FALHOU(Payload: motivo=”Saldo Insuficiente”)Novo Estado:
RETRY_PAYMENTTimestamp: 14:00:01
Isso transforma a depuração. Em vez de adivinhar o comportamento do sistema cruzando logs de dez microsserviços, você olha para a tabela de transições e vê a narrativa exata da decisão tomada pela máquina. Isso não é apenas métrica para PMs; é ferramenta de troubleshooting para engenheiros e prova de conformidade para auditores.
A Diferença entre Código e Arquitetura
Chegamos ao fim desta jornada. Se você leu até aqui, percebeu que implementar uma Máquina de Estados em um sistema financeiro não é um preciosismo acadêmico. É uma estratégia de sobrevivência.
Ao longo deste texto, desconstruímos a ilusão de que “basta uma coluna de status e alguns IFs”. Vimos que a complexidade do crédito — juros, atrasos, bloqueios judiciais — não pode ficar espalhada em Controllers ou Services. Ela precisa de um lar. E esse lar é dividido em duas casas: o Motor de Decisão, que pensa a estratégia, e a Máquina de Estados, que protege a integridade.
O Que Levar Daqui
Se este artigo mudar apenas três coisas na sua próxima implementação, que sejam estas:
A FSM é o Guardião: Ela é o único componente do seu sistema que tem o direito de dizer “NÃO”. Quando o Motor, o Frontend ou o CEO pedirem uma mudança de status, a FSM tem a autoridade de barrar se aquilo violar o ciclo de vida do contrato. Ela impede o caos dos “Estados Impossíveis”.
O Gatilho é a Única Porta de Entrada: Acabou a era do
UPDATE contracts SET status = .... A partir de hoje, você interage com o contrato através de intenções claras (PAGAMENTO_RECEBIDO,FALHA_DETECTADA). Isso garante que nenhuma regra seja burlada e que todo evento deixe um rastro auditável.A Transição é Regra de Negócio, não Seta: Uma transição não é apenas um caminho de A para B. Com o uso de Guards e Payloads, a transição torna-se o local onde a lógica vital reside. Ela decide se o dinheiro recebido é suficiente para quitar ou apenas amortizar. Ela centraliza a inteligência do fluxo.
A maior barreira para adotar este padrão não é técnica, é mental. O desenvolvedor júnior foca na Persistência: “Preciso salvar que o cliente pagou.” O arquiteto sênior foca no Comportamento: “Dado que o cliente pagou, quais caminhos são válidos e quais garantias eu preciso oferecer?”
Adotar Máquinas de Estados é aceitar pagar um preço inicial maior na modelagem para comprar tranquilidade a longo prazo. É a diferença entre passar suas noites corrigindo dados inconsistentes no banco de dados ou dormir tranquilo, sabendo que sua arquitetura matematicamente proíbe que o erro aconteça.
O código muda, as regras de negócio mudam, mas a integridade dos dados do seu cliente é inegociável. Construa sistemas que respeitem isso.
FAQ & Trade-offs
Se você acompanhou até aqui, é provável que algumas luzes de alerta tenham acendido na sua cabeça. Arquitetura de software é feita de trade-offs (trocas), e implementar uma FSM resiliente com Outbox traz complexidades que não podem ser ignoradas.
Vamos antecipar as dúvidas (e evitar discussões teóricas infinitas no Code Review ou no LinkedIn):
1. “Isso é Event Sourcing?”
Resposta Curta: Não.
A Nuance: Muita gente confunde os conceitos.
Event Sourcing: O estado da aplicação é derivado exclusivamente da re-execução de todos os eventos passados. Você não tem uma coluna
statusno banco; você tem um log de eventos.FSM + Outbox (O que propomos aqui): O estado continua sendo persistido da maneira tradicional (Snapshot State). Você tem a coluna
statusatualizada viaUPDATE. A tabela de eventos (Outbox) serve apenas para integração com outros sistemas, não para reconstruir o estado interno.
“O Outbox Pattern serve para garantir que o mundo externo saiba o que aconteceu, sem que você precise mudar a forma como seu banco de dados relacional funciona.”
2. “O Outbox garante que eu receba a mensagem apenas uma vez?”
Resposta Curta: Absolutamente não. E isso é perigoso.
A Nuance: O Outbox resolve o problema de perder mensagens, mas introduz a possibilidade de duplicar mensagens. O worker que lê a tabela Outbox pode falhar logo após enviar para o Kafka, mas antes de marcar a mensagem como “enviada” no banco. Quando ele reiniciar, ele enviará a mesma mensagem novamente.
Isso é o que chamamos de garantia At-Least-Once Delivery (Entrega pelo menos uma vez). Consequência: Todos os consumidores (serviços de Cartão, Cobrança, etc.) DEVEM ser Idempotentes. Se o serviço de Cartão receber o evento ContractBlocked duas vezes, na segunda vez ele deve olhar e dizer: “Ok, já está bloqueado, não vou fazer nada”, e não travar ou gerar erro.
3. “Por que a FSM não pode chamar uma API externa (HTTP) para decidir?”
Dúvida: “Se o cliente está atrasado há 150 dias, por que a FSM não faz um GET na API de Calendário ou na API de Score para saber o que fazer?”
Resposta: Porque isso viola o isolamento e acopla a disponibilidade. Se a API de Score estiver fora do ar, sua FSM trava e você não consegue mais transitar contratos. A FSM deve ser autocontida.
Errado: FSM pergunta: “Quantos dias de atraso?”
Correto: Um Scheduler externo calcula os dias e envia o gatilho:
Fire(ATRASO_150_DIAS_DETECTADO). A FSM não calcula, ela reage. Ela não pensa, ela executa regras pré-definidas.
4. “Por que tanta complexidade com Tabela Outbox? O Kafka quase nunca cai.”
A Realidade: “Quase nunca” em sistemas financeiros de alta escala significa “vai cair na Black Friday”.
O custo de implementar o Outbox é o custo de configurar um worker extra. O custo de não implementar é ter dados inconsistentes (dinheiro saindo da conta sem registro) e ter que gastar semanas rodando scripts manuais de correção no banco de dados para conciliar sistemas.
Em arquitetura, você paga o preço da complexidade agora para não pagar o preço do caos às 3 da manhã de um domingo.
Simplificando, a teoria dos autômatos lida com a lógica da computação em relação a máquinas simples, chamadas de autômatos . Através dos autômatos, os cientistas da computação conseguem entender como as máquinas computam funções e resolvem problemas e, mais importante, o que significa uma função ser definida como computável ou uma questão ser descrita como decidível .
O Botão do Elevador
Para visualizar a função do Gatilho, imagine que você está dentro de um elevador subindo entre o 10º e o 20º andar.
O elevador está no Estado: EM_MOVIMENTO. Você decide apertar o botão “Abrir Porta” (Gatilho).
O painel recebe o sinal elétrico do botão (a solicitação externa). No entanto, a porta não se abre. O sistema central do elevador verifica o estado atual (EM_MOVIMENTO), consulta suas regras de segurança e decide ignorar o gatilho.
A lição: O Gatilho é apenas o pedido (a intenção). Ele não tem poder absoluto. Quem tem a palavra final sobre se a porta abre ou não é a combinação do Estado Atual com as Regras de Transição.
A Fronteira entre “Guard” e “Motor de Regras”
Leitores atentos podem notar uma aparente contradição. No tópico “Anatomia de uma Máquina de Estados”, afirmamos que a FSM não deve conter lógicas como if (diasAtraso > 10...). Agora, sugerimos que ela compare valor_pago com valor_total. Qual é a diferença?
A diferença é Cálculo vs. Roteamento.
O que a FSM NÃO faz (Cálculo/Política): A FSM não decide qual é a taxa de juros, nem se 10 dias de atraso é ruim ou bom. Isso é política de crédito (variável) e pertence ao Motor de Decisão.
O que a FSM DEVE fazer (Roteamento/Fato): A FSM recebe fatos. Comparar se
150.00 == 150.00não é uma regra de negócio complexa; é uma validação de integridade estrutural.A Guard pergunta: “O valor que chegou preenche o buraco total da dívida?”
Se Sim -> Caminho A (
SIGNED).Se Não -> Caminho B (
PARTIALLY_PAID).
Por que enviar os valores no Payload? Mesmo que tenhamos o campo status_final_fatura, enviar os valores brutos (150.00) serve como Prova de Auditoria dentro do evento. Se houver um bug no faturamento que envie status: TOTAL_PAID mas com valor: 0.00, uma Guard bem escrita na FSM pode pegar essa inconsistência (valor_pago != valor_total) e travar a transição, salvando o sistema de um falso positivo.









