Quando o CRUD comum já não é suficiente: conhecendo CQRS e Event Sourcing
\nImagine que você está construindo um sistema de reserva de ingressos. Tudo é simples enquanto há dez usuários. Mas quando se tornam mil, e depois um milhão, começam os problemas: bancos de dados são bloqueados, consultas entram em conflito e o histórico de alterações não pode ser recuperado. Situação familiar?
\nNo mundo do desenvolvimento Go, onde desempenho e concorrência são tudo, dois padrões arquiteturais ajudam a resolver esses problemas: CQRS (Command Query Responsibility Segregation) e Event Sourcing. Hoje vamos analisá-los na prática — com código, exemplos e cenários reais de uso.
\n\nO que é CQRS em termos simples?
\nCQRS é a separação de responsabilidades entre comandos (escrita) e consultas (leitura). No CRUD clássico, um único modelo de dados é responsável tanto pela criação quanto pela leitura. No CQRS, criamos dois modelos separados:
\n- \n
- Modelo de Comando — responsável por alterar o estado (criação, atualização, exclusão) \n
- Modelo de Consulta — responsável apenas pela leitura de dados \n
Isso permite otimizar cada lado de forma independente: a escrita pode ser estritamente validada, e a leitura pode ser desnormalizada e rápida. Em Go, isso é especialmente eficiente graças às goroutines e canais.
\n\nExemplo simples de CQRS em Go
\nVamos criar um sistema básico para gerenciamento de pedidos. Começamos com os comandos:
\n\ntype CreateOrderCommand struct {\n OrderID string\n UserID string\n ProductID string\n Amount float64\n}\n\ntype CommandHandler struct {\n repo *OrderRepository\n}\n\nfunc (h *CommandHandler) HandleCreateOrder(ctx context.Context, cmd CreateOrderCommand) error {\n order := Order{\n ID: cmd.OrderID,\n UserID: cmd.UserID,\n ProductID: cmd.ProductID,\n Amount: cmd.Amount,\n Status: "pending",\n CreatedAt: time.Now(),\n }\n return h.repo.Save(ctx, order)\n}\n\nAgora criamos um modelo separado para leitura:
\n\ntype OrderQuery struct {\n repo *ReadRepository\n}\n\ntype OrderView struct {\n OrderID string\n UserName string\n ProductName string\n Total float64\n Status string\n}\n\nfunc (q *OrderQuery) GetOrderSummary(ctx context.Context, orderID string) (*OrderView, error) {\n return q.repo.GetOrderView(ctx, orderID)\n}\n\nObserve: em OrderView já existem dados desnormalizados (nome do usuário, nome do produto), que são coletados de diferentes fontes. Isso acelera a leitura dezenas de vezes.
Event Sourcing: registramos não o estado, mas os eventos
\nEvent Sourcing muda a abordagem de armazenamento de dados: em vez de armazenar o estado atual de um objeto, armazenamos a sequência de eventos que levaram a esse estado. Isso dá um superpoder: histórico completo de alterações e a capacidade de "rebobinar" o estado.
\n\nImplementação de Event Sourcing em Go
\nComeçamos com os tipos básicos de eventos:
\n\ntype Event struct {\n ID string\n AggregateID string\n Type string\n Data []byte\n Timestamp time.Time\n Version int\n}\n\ntype EventStore interface {\n Save(ctx context.Context, events []Event) error\n Load(ctx context.Context, aggregateID string) ([]Event, error)\n}\n\nAgora implementamos o agregado — o objeto que restaura seu estado a partir dos eventos:
\n\ntype OrderAggregate struct {\n ID string\n Status string\n Items []OrderItem\n Version int\n}\n\nfunc (a *OrderAggregate) Apply(event Event) {\n switch event.Type {\n case "OrderCreated":\n var data OrderCreatedData\n json.Unmarshal(event.Data, &data)\n a.ID = data.OrderID\n a.Status = "created"\n case "OrderConfirmed":\n a.Status = "confirmed"\n case "OrderShipped":\n a.Stat