O que é event sourcing?

Prévia grátis 9 min de leitura

Um banco que não sabe se explicar

Sua conta mostra R$2.847,63. Você esperava R$3.347,63. Você liga para o banco e pergunta para onde foram os R$500. O banco responde: "Não sabemos. Só guardamos o seu saldo atual."

Seria assustador, e mesmo assim é assim que a maior parte dos softwares funciona.

Uma aplicação Rails tradicional, de CRUD, guarda o estado atual de cada registro. Quando alguém atualiza o perfil, você sobrescreve os dados antigos. Quando um pedido passa de pending para shipped, você roda order.update!(status: :shipped), e o estado anterior, pending, some. Se alguém perguntar "quando esse pedido foi enviado?" ou "quem mudou isso?", você fica sem resposta, a não ser que tenha montado um log de auditoria à parte.

O event sourcing inverte essa lógica. Em vez de guardar o estado atual, você guarda cada mudança que já aconteceu, como uma sequência de eventos imutáveis. O estado atual nem é guardado: ele é derivado reproduzindo esses eventos.


Baseado em estado vs baseado em eventos: dois modelos mentais

Pense num carrinho de compras simples numa app Rails tradicional:

# Abordagem CRUD — armazena estado atual
cart = Cart.find(42)
cart.items << Item.new(product_id: 7, quantity: 2)
cart.save!

# Depois...
cart.items.find_by(product_id: 7).update!(quantity: 3)

# Mais tarde...
cart.items.find_by(product_id: 7).destroy!

Depois dessas operações, o banco mostra um carrinho vazio. O que aconteceu no caminho (um item adicionado, a quantidade alterada, o item removido) se perdeu.

Agora o mesmo cenário com event sourcing:

# Abordagem event-sourced — armazena toda mudança
event_store.append(ItemAddedToCart.new(
  cart_id: 42, product_id: 7, quantity: 2
))

event_store.append(CartItemQuantityChanged.new(
  cart_id: 42, product_id: 7, new_quantity: 3
))

event_store.append(ItemRemovedFromCart.new(
  cart_id: 42, product_id: 7
))

O carrinho continua vazio, mas agora você sabe por quê. Dá para responder "O que o cliente quase comprou?", "Quando ele mudou de ideia?" e "Com que frequência os clientes removem itens depois de mudar a quantidade?". O CRUD não responde a essas perguntas. O event sourcing responde com facilidade.

flowchart LR
    subgraph CRUD["CRUD (Baseado em Estado)"]
        direction TB
        C1["tabela cart_items"]
        C2["Estado Atual: (vazio)"]
        C3["Auditoria: O que aconteceu? Nenhuma ideia."]
        C1 --> C2 --> C3
    end

    subgraph ES["Event Sourcing"]
        direction TB
        E1["stream de eventos"]
        E2["1. ItemAdded"]
        E3["2. QtyChanged"]
        E4["3. ItemRemoved"]
        E5["Auditoria: Histórico completo preservado."]
        E1 --> E2 --> E3 --> E4 --> E5
    end

A analogia do livro-razão

O event sourcing não é novidade. Os contadores trabalham assim há mais de 700 anos.

Na contabilidade de partidas dobradas, você nunca apaga um lançamento. Se errou, faz um lançamento de correção. O saldo atual é sempre a soma de todos os lançamentos, então dá para auditar cada transação e reconstruir o saldo de qualquer momento.

O event sourcing faz o mesmo com o software:

# Livro-razão contábil (event-sourced por natureza)
class LedgerEntry
  # date, description, debit, credit
end

balance = entries.sum { |e| e.credit - e.debit }

# Event store de software (mesmo princípio)
class AccountEvent
  # timestamp, event_type, data
end

current_state = events.reduce(initial_state) do |state, event|
  apply(state, event)
end

Uma frase resume a ideia: "o log é a verdade". Os eventos são o registro oficial. Todo o resto (o saldo atual, o conteúdo do carrinho, o status do pedido) é uma visão derivada que pode ser reconstruída a qualquer hora.


Onde o Rails entra nisso

O Rails é construído em torno do ActiveRecord, que é baseado em estado por natureza: você chama save! e a linha é sobrescrita. Event sourcing não significa abandonar o Rails, porém. Significa modelar certas partes do domínio de outro jeito.

Este é um padrão mínimo de event sourcing numa app Rails:

# app/models/event.rb
class Event < ApplicationRecord
  # tabela: events
  # colunas: id, stream_name, event_type,
  #          data (jsonb), metadata (jsonb),
  #          created_at

  validates :stream_name, :event_type, presence: true

  scope :for_stream, ->(name) { where(stream_name: name) }
  scope :ordered, -> { order(:id) }
end

# Publicando um evento
Event.create!(
  stream_name: "Order-#{order_id}",
  event_type: "OrderPlaced",
  data: { customer_id: 42, total: 99.99 },
  metadata: { actor_id: current_user.id }
)

# Reconstruindo o estado atual
events = Event.for_stream("Order-#{order_id}").ordered
order_state = events.reduce({}) do |state, event|
  case event.event_type
  when "OrderPlaced"
    state.merge(status: :placed, total: event.data["total"])
  when "OrderShipped"
    state.merge(status: :shipped, shipped_at: event.created_at)
  when "OrderCancelled"
    state.merge(status: :cancelled)
  else
    state
  end
end

Isso não é event sourcing pronto para produção; gems como a rails_event_store fornecem a infraestrutura de verdade. Mas mostra a ideia central: acrescentar eventos e derivar o estado.


Quando o event sourcing é essencial

O event sourcing compensa a complexidade quando o seu domínio tem estas características.

A auditoria é inegociável

Sistemas financeiros, prontuários médicos, documentos jurídicos. O regulador pergunta "mostre cada mudança que levou a este estado". CRUD com log de auditoria é frágil, porque o log pode se afastar da realidade. Com event sourcing, os eventos são a realidade.

Perguntas sobre o passado importam

"Em que plano esse cliente estava no dia 15 de março?" No CRUD, você torce para alguém ter criado uma tabela versions. Com event sourcing, você reproduz os eventos até aquela data.

O negócio depende do comportamento ao longo do tempo

Análise de carrinhos abandonados, mapa da jornada do usuário, detecção de fraude. Tudo isso precisa saber o que aconteceu, e não só o que é.

Várias pessoas editam a mesma coisa

Google Docs, Figma, jogos multiplayer: várias pessoas mudam a mesma entidade ao mesmo tempo. Eventos (operações) podem ser combinados e reproduzidos; estado sobrescrito, não.


Quando o event sourcing é exagero

Tem uma coisa que os defensores do event sourcing quase nunca dizem em voz alta: a maioria das aplicações não deveria usá-lo.

Cenário Event sourcing? Por quê
Blog com posts e comentários Não CRUD é mais simples e basta. Não há necessidade de auditoria.
Gestão de perfis de usuário Não Sobrescrever um nome não tem problema. Ninguém precisa do histórico.
Painel interno de administração Não Pouca complexidade e pouca exigência de auditoria.
Fluxo de pedidos de e-commerce Sim Trilha de auditoria, perguntas sobre o passado, mudanças de estado complexas.
Transações financeiras Sim A lei exige o histórico completo.
Editor colaborativo de documentos Sim Edições simultâneas precisam do histórico de operações.
Matrícula simples num LMS de CRUD Não Uma flag booleana completed basta.
Livro-razão de XP num LMS gamificado Sim Registros imutáveis, só de acréscimo. Encaixe natural.

O model Enrollment::ExperiencePoint deste LMS segue o espírito do event sourcing. Cada XP concedido é um registro imutável, e o total sai da soma deles. Você nunca atualiza um registro de XP, só acrescenta novos. Isso é event sourcing sem a infraestrutura completa. Um detalhe: nada no model impede uma atualização, então "imutável" aqui é uma convenção. Sobrescrever readonly? para devolver persisted? transformaria a convenção em regra.


A matriz de trade-offs

Event sourcing não sai de graça. Veja o que você ganha e o que paga:

Dimensão CRUD Event sourcing
Armazenamento Compacto: uma linha por entidade Cresce sem limite: toda mudança fica guardada
Velocidade de leitura Rápida: consulta o estado atual direto Mais lenta sem projeções: é preciso reproduzir
Velocidade de escrita Moderada: lê, altera, grava Rápida: só acrescenta, mas escritas simultâneas no mesmo stream precisam de lock otimista
Trilha de auditoria Precisa de uma implementação à parte Já vem junto: os eventos são a trilha de auditoria
Depuração "Qual é o estado?" é fácil; "Como chegou aqui?" é difícil O log de eventos responde às duas
Complexidade Baixa: conhecida e nativa do Rails Alta: projeções, consistência eventual, versionamento de eventos
Habilidades do time Conhecimento padrão de Rails Um jeito diferente de pensar para o time inteiro
Mudanças de schema Migrations alteram os dados existentes Eventos são imutáveis, então evoluir o schema é mais difícil

O custo organizacional é o que os times mais subestimam. O event sourcing muda o jeito como os desenvolvedores pensam os dados. Se o seu time de dez desenvolvedores Rails nunca trabalhou com isso, conte com uma queda real de produtividade enquanto ele se adapta, muitas vezes de meses.


Erros comuns

Aplicar event sourcing na aplicação inteira

Noventa por cento da sua app é CRUD simples: páginas de configuração, painéis de admin, perfis de usuário. Embrulhar tudo isso em eventos só acrescenta cerimônia.

Em vez disso, use event sourcing só nos domínios em que histórico, auditoria ou perguntas sobre o passado importam. O resto continua como CRUD padrão do Rails.

Tratar event sourcing como "só um log de auditoria"

Um log de auditoria é um registro secundário do que aconteceu. No event sourcing, os eventos são o registro principal, e a tabela de estado atual é que é secundária. Confunda esses papéis e os eventos e o estado vão se afastar.

Em vez disso, faça dos eventos a fonte da verdade. Derive todo o estado a partir dos eventos, nunca o contrário.

Guardar eventos que ninguém lê

Evento que ninguém lê é peso morto: armazenamento e complexidade por nada.

Em vez disso, antes de criar um tipo de evento, aponte pelo menos um consumidor real que precise dele: uma projeção, um relatório, uma notificação.


Resumo

Conceito O que guardar
CRUD (baseado em estado) Guarda só o estado atual; o histórico se perde a cada atualização
Event sourcing Guarda cada mudança como um evento imutável; o estado atual é derivado
O log é a verdade Os eventos são o registro oficial; as tabelas de estado são visões derivadas
Livro-razão A contabilidade funciona assim há mais de 700 anos
Quando usar Trilhas de auditoria, sistemas financeiros, perguntas sobre o passado, edição colaborativa
Quando evitar CRUD simples, painéis de admin, perfis de usuário, blogs
Principal trade-off Você ganha histórico completo e auditoria; paga com complexidade e armazenamento
Custo organizacional Muda o jeito de pensar do time; conte com meses de produtividade menor
Aula 1 de 7 · grátis

Continue com a aula 2

Eventos como cidadãos de primeira classe

Trata de nomes no passado, schemas com valores do momento, metadados, imutabilidade e três estratégias de versionamento de eventos.

Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.

Digite para buscar...