Por que CQRS e Event Sourcing não são "exagero", mas sim uma salvação para sistemas complexos
\n\nVocê já se deparou com uma situação em que seu sistema, escrito em Rust, lida perfeitamente com solicitações de leitura, mas "cai de joelhos" na gravação? Ou vice-versa — você sacrifica o desempenho da leitura para poder salvar o histórico completo de alterações? Familiar? Então bem-vindo ao mundo do CQRS e do Event Sourcing.
\n\nMuitos desenvolvedores consideram esses padrões como "artilharia pesada", aplicável apenas em projetos enterprise em C# ou Java. Mas com o crescimento da popularidade do Rust em sistemas de alta carga (de servidores de jogos a plataformas financeiras), CQRS e Event Sourcing tornam-se não apenas úteis — eles se tornam necessários. Rust, com seu custo zero de abstrações e tipagem estrita, é a linguagem ideal para implementar esses padrões.
\n\nNeste artigo, não vamos apenas falar sobre teoria. Vamos escrever código funcional em Rust que você poderá adaptar para seus projetos. Você aprenderá como separar a responsabilidade entre comandos e consultas, como armazenar eventos em vez do estado atual e como tudo isso acelera seu sistema várias vezes.
\n\nO que são CQRS e Event Sourcing? Em linguagem simples para o desenvolvedor Rust
\n\nImagine que você gerencia uma conta bancária. No modelo CRUD tradicional, você tem uma tabela "Contas" onde o saldo atual é armazenado. Se precisar ver o histórico, você cria outra tabela "Transações". Mas e se você precisar não apenas saber o saldo, mas entender por que ele mudou? Ou restaurar o estado em qualquer data?
\n\nCQRS: Dividir para conquistar
\n\nCQRS (Command Query Responsibility Segregation) é um princípio que divide as operações em dois grupos:
\n- \n
- Comandos (Commands) — alteram o estado. Eles não retornam dados, apenas confirmação ou erro. \n
- Consultas (Queries) — apenas leem dados. Eles não alteram o estado. \n
Em Rust, essa divisão parece natural: os tipos de comandos e consultas são estruturas diferentes, e seus manipuladores são funções ou módulos diferentes. Isso não é apenas uma "boa prática", é uma maneira de evitar mutações acidentais durante a leitura.
\n\n// Comando — altera o estado\npub struct CreateOrder {\n pub user_id: Uuid,\n pub items: Vec<OrderItem>,\n}\n\n// Consulta — apenas lê\npub struct GetOrderHistory {\n pub user_id: Uuid,\n pub limit: usize,\n}\n\nEvent Sourcing: Armazene o histórico, não uma foto instantânea
\n\nEvent Sourcing é uma abordagem onde salvamos não o estado atual de um objeto, mas a sequência de eventos que levaram a ele. Quer saber o saldo da conta em 1º de janeiro de 2023? Basta "reproduzir" todos os eventos desde o início até essa data.
\n\nAs vantagens são óbvias:
\n- \n
- Auditoria — cada alteração é registrada. \n
- Instantâneos temporais — é possível restaurar qualquer estado. \n
- Resistência a bugs — se houver um erro no código, você não perdeu os dados, apenas os "reproduziu" incorretamente. \n
Exemplo prático: Sistema de gerenciamento de pedidos em Rust
\n\nVamos construir um mini-sistema para uma loja online. Ele processará comandos (criação de pedido, adição de item) e consultas (obter carrinho, histórico de pedidos). Toda a lógica será baseada em eventos.
\n\nPasso 1: Definindo eventos e comandos
\n\nA primeira coisa a fazer é projetar o domínio. Em Rust, usamos enumerações (enum) para eventos e estruturas estritas para comandos.
use serde::{Deserialize, Serialize};\nuse uuid::Uuid;\n\n// Eventos — fatos imutáveis\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub enum OrderEvent {\n Created { order_id: Uuid, user_id: Uuid, timestamp: i64 },\n ItemAdded { order_id: Uuid, item: Str