Como implementar um Ledger
O que é um ledger?
Considere um Ledger como um “livro da verdade”, por exemplo, uma planilha contábil de uma empresa, onde são registradas todas as transações que aconteceram. Assim como em uma planilha contábil, não editamos ou removemos registros, apenas adicionamos novas entradas. Isso nos garante a imutabilidade, que é um ótimo princípio em sistemas financeiros. A forma mais comum de tratar isso é por meio de eventos de entrada e saída, junto ao montante de valor correspondente para cada transação.
Como fazer?
Minha recomendação é a abordagem de Event Sourcing com snapshots. A ideia é ter uma entidade responsável por armazenar as entradas, que deve ser “append-only”. Muitos bancos de dados SQL e NoSQL fornecem mecanismos para permitir apenas escritas incrementais e leituras.
Através dessas entradas cronológicas, podemos recriar o estado atual do Ledger e obter informações como seu saldo. Mas e quando o Ledger possui milhões de transações? É aqui que entram os snapshots. A ideia é tirar uma “fotografia” do estado atual periodicamente, salvando-o como a versão mais confiável. Poderíamos, por exemplo, salvar diariamente uma versão pré-calculada do saldo do Ledger. Para obter o saldo atual, somamos o saldo da última snapshot com as novas entradas desde então, evitando a necessidade de reprocessar todas as transações desde o início.
Persistência e Concorrência
A persistência do Ledger pode ser implementada em um banco de dados como o MongoDB. Por ser NoSQL e orientado a documentos, ele se encaixa bem na abordagem de Event Sourcing, com uma coleção para eventos e outra para snapshots.
Para garantir a consistência e evitar problemas de concorrência na gravação de eventos, existem técnicas no nível do banco de dados. Uma delas é o uso de bloqueios otimistas, onde você verifica a versão de um documento antes de atualizá-lo para garantir que nenhuma outra transação o modificou no meio tempo. Outra forma é utilizar bloqueios externos, como um Redis Lock, que pode serializar as escritas para um Ledger específico, garantindo que apenas uma transação por vez modifique seu estado. Essas técnicas são cruciais para manter a integridade dos dados, especialmente em sistemas distribuídos.
Como ficam meus dados?
Seguindo a recomendação da persistência no MongoDB podemos usar duas coleções,uma para armazenar os eventos e outra para os snapshots.
Coleção de Eventos (append-only)
Esta é a base do nosso Ledger. Cada nova transação é um documento nesta coleção.
{
"_id": "string",
"ledger_id": "string",
"event_type": "string",
"timestamp": "ISODate",
"payload": {
"amount": "number",
"currency": "string",
},
"version": "number"
}
Coleção de Snapshots
Aqui guardamos o estado pré-calculado do Ledger para otimizar as consultas.
{
"_id": "string",
"ledger_id": "string",
"balance": {
"amount": "number",
"currency": "string"
},
"last_event_id": "string",
"created_at": "ISODate"
}
Considerações Finais
É extremamente importante usarmos mecanismos de idempotência para evitarmos entradas duplicadas. Uma forma simples e eficiente de garantir isso é usando um identificador de transação único fornecido pelo cliente para cada operação.
Além disso, a ordenação dos eventos é crucial. A recomendação é usar um mecanismo de ordenação eficiente com fallbacks, por exemplo, timestamp
+ uuidV7
(que possui ordenação padrão). A ordem dos eventos é a única verdade para determinar o estado do Ledger. Se a ordenação falhar, o estado final será inválido.
No fim das contas, a imutabilidade e o conceito “append-only” são a base para construir sistemas financeiros robustos. A solução de Event Sourcing com snapshots resolve problemas de escala e garante que o Ledger permaneça como o “livro da verdade” do seu sistema.