O livro de registros do cartório
Em cartório, ninguém apaga um assento errado — faz-se uma averbação: um registro novo, datado e assinado, dizendo 'o anterior fica retificado assim'. O erro continua visível, a correção também, e qualquer um pode reconstituir a história folheando em ordem. Rasura em cartório não corrige: incrimina.
O problema: o UPDATE é um destruidor de evidências
Toda vez que uma linha é sobrescrita, uma versão do mundo deixa de existir. No dia em que o suporte pergunta 'o endereço de entrega mudou antes ou depois do despacho?', a resposta já foi destruída pela própria operação que causou a dúvida.
E o custo não é só forense: sistemas que precisam reagir a mudanças (réplicas, caches, buscas, times vizinhos) não têm o que consumir — sobra CDC ou gambiarras de polling comparando snapshots.
Você já depende de append-only mesmo sem saber: todo banco relacional grava primeiro num WAL (write-ahead log) e só depois aplica nas páginas — é o que torna crash recovery e replicação possíveis. Kafka é um log distribuído com retenção. O git nunca edita um commit: 'amend' cria outro objeto e move o ponteiro. A ideia sustenta a infraestrutura inteira; o padrão é trazê-la para o seu modelo de dados quando a história importa.
A diferença para Event Sourcing: append-only é a propriedade da estrutura (só anexa); Event Sourcing é uma decisão de arquitetura que usa essa estrutura como fonte única da verdade do domínio. Dá para ter uma tabela de auditoria append-only ao lado de um CRUD normal — sem adotar Event Sourcing.
Log (append-only)
registro 1
registro 2
registro 3 ← única posição de escrita
Projeções derivadas
estado atual
índice de busca
As três responsabilidades
Camada 1/3Escrita
use ← →Camada 1
Escrita
anexar no fim — e mais nada
Um único ponto de contenção (a posição final), o que torna a escrita sequencial e rápida. A imutabilidade merece ser imposta no armazenamento: REVOKE de UPDATE/DELETE, tabela particionada por tempo, ou um broker de log como Kafka.
Se violar: um único registro editado 'por emergência' invalida o valor probatório do log inteiro — se um pôde mudar, todos podem ter mudado.
Código
// Um log append-only: a única operação de escrita é "acrescentar no fim".
interface Registro {
seq: number; // posição no log — nunca reaproveitada
tipo: string;
dados: Record<string, unknown>;
em: string; // timestamp ISO
}
const log: Registro[] = [];
function anexar(tipo: string, dados: Record<string, unknown>): Registro {
const r = { seq: log.length + 1, tipo, dados, em: new Date().toISOString() };
log.push(r); // sem splice, sem sobrescrever: o passado é intocável
return r;
}
// Correção não edita o registro errado — SUPERA com um registro novo:
anexar("email-alterado", { usuario: "ana", email: "ana@exemplo.com" });
anexar("email-alterado", { usuario: "ana", email: "ana@empresa.com" }); // vale o mais recente
// O estado atual é uma PROJEÇÃO derivada do log, não a fonte da verdade:
function emailAtual(usuario: string): string | undefined {
let email: string | undefined;
for (const r of log) {
if (r.tipo === "email-alterado" && r.dados.usuario === usuario) {
email = r.dados.email as string;
}
}
return email; // e o log inteiro responde "quando mudou, de quê, para quê"
}
console.log(emailAtual("ana")); // ana@empresa.com — com a história preservadaCasos de uso
Trilha de auditoria de um sistema financeiro
Cenário
Compliance exige responder 'quem alterou o limite deste cliente, quando e de quanto para quanto' por cinco anos — e provar que o registro não foi adulterado depois.
Como o padrão entra
Toda mutação relevante grava um registro append-only (ator, antes, depois, timestamp) numa tabela sem UPDATE/DELETE, com hash encadeado opcional para evidenciar adulteração. O CRUD continua normal; a auditoria é um subproduto barato de cada transação.
Trade-off: Escrita dupla em toda operação auditada e um volume de dados que cresce sem teto — retenção e arquivamento em storage frio viram tarefa de rotina.
Log de eventos entre times
Cenário
O time de pedidos precisa avisar estoque, nota fiscal e recomendação a cada venda — sem acoplar seu deploy ao ritmo dos três consumidores.
Como o padrão entra
Pedidos publica eventos num tópico com retenção de dias; cada consumidor mantém seu offset e processa no próprio ritmo. Um consumidor novo (ou um reprocessamento após bug) simplesmente lê o log desde o início — o passado está todo lá.
Trade-off: Consumidores atrasados leem um mundo defasado (consistência eventual), e o schema dos eventos vira contrato público: evoluí-lo sem quebrar leitores antigos exige disciplina de versionamento.
Armadilhas
O UPDATE 'só desta vez'
Produção pegando fogo, alguém edita um registro direto no banco para 'corrigir rápido'. A partir daí o log não prova mais nada — auditoria que aceita exceção não é auditoria. Corrija como o modelo manda: um registro novo de retificação, com motivo.
Imutabilidade × LGPD sem plano
Direito de esquecimento e log imutável colidem de frente. Quem guarda dado pessoal em log precisa decidir no design: criptografar por titular e descartar a chave (crypto-shredding), registrar tombstones, ou manter o pessoal fora do log. Descobrir isso no primeiro pedido de exclusão é tarde.
Crescimento infinito 'para depois'
Sem política de retenção, o log cobra em disco, em backup e em tempo de replay. Compactação, snapshot e arquivamento não são otimização prematura — são parte do modelo, decididas junto com ele.
Quando usar × quando evitar
Use quando
3- Histórico com valor de prova: auditoria, financeiro, saúde, jurídico.
- Integração via eventos onde consumidores leem em ritmos diferentes.
- Debug e replicação — reconstruir qualquer estado passado lendo o log.
Evite quando
3- Estado quente reescrito milhares de vezes sem ninguém ler a história.
- Dados pessoais sob exclusão obrigatória, sem crypto-shredding planejado.
- Times sem apetite para operar retenção e projeções — um CRUD com tabela de auditoria pode bastar.
Testar o que ficou
3 armadilhas de Append-only viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.
Onde isto aparece de verdade
Você já usa isto, talvez sem saber o nome.
WAL do PostgresO write-ahead log registra cada mudança em sequência antes de aplicá-la; é acréscimo puro, e a recuperação relê o log do ponto seguro.tópicos do KafkaMensagens são anexadas ao fim da partição e nunca alteradas; os consumidores avançam um offset em vez de o produtor apagar o que passou.objetos do gitCommits e blobs são endereçados pelo hash do conteúdo, então gravar é sempre acrescentar um objeto novo — o antigo continua lá, imutável.
O padrão em uma linha
Sem nome de domínio, sem cerimônia. Só a ideia.
// Só append — nunca UPDATE no fato passado.
await log.append({ tipo: "SaldoCreditado", valor: 10 });O que ele cobra
Nenhum salto a mais entre a chamada e o efeito.
- O armazenamento só cresce, o que exige compactação, retenção ou arquivamento com o tempo
- Ler o estado atual pode precisar percorrer ou reduzir o log inteiro sem uma projeção
Não vale se: o dado é naturalmente mutável e sem valor histórico — guardar todas as versões de algo descartável só ocupa espaço.
Onde isto já custou caro
Incidentes reais que este conceito explica.
Onde isto aparece
Continue por aqui
- Ledger (livro-razão)Em vez de um saldo que muda, um histórico imutável de lançamentos — e o saldo é a soma. Com partidas dobradas, todo valor sai de uma conta e entra em outra, e a soma do sistema é sempre zero: dinheiro não aparece nem some sem rastro.
- Event SourcingEm vez de guardar o estado atual, o sistema guarda a sequência imutável de eventos que o produziu — o estado é sempre derivado por replay, e o histórico é a fonte da verdade.
- WebhooksEm vez de perguntar 'já aconteceu?' de minuto em minuto, você registra uma URL e o outro sistema te chama quando acontece. É o Observer atravessando a internet — com tudo que a rede cobra por isso: assinatura, retry e duplicata.