Agosto de 2015

Número 8 do Volume 30

Cutting Edge - CQRS e eventos: Uma dupla poderosa

Por Dino Esposito | Agosto de 2015

Dino EspositoAssim como muitos aspectos do software moderno, nada é realmente novo. As coisas são, em geral, novamente rotuladas e exibidas com jargões novos e interessantes. A Command Query Responsibility Segregation (CQRS - Segregação de Responsabilidade Comando-Consulta) é um ótimo exemplo. Eventos de domínio que descrevem alterações observáveis no domínio corporativo é outro exemplo.

Ao definir a linguagem de programação Eiffel na década de 80, Bertrand Meyer formalizou o princípio na base do que hoje é chamado CQRS. No software, qualquer ação básica pode ser um comando ou uma consulta, mas nunca os dois. Se ela for um comando, espera-se que altere o estado do sistema. Se ela for uma consulta, espera-se que ela informe sobre o estado do sistema sem alterá-lo de forma alguma.

Em outras palavras, fazer uma pergunta não deve alterar a resposta. Isso é às vezes chamado como o princípio de Separação de Comando-Consulta (CQS). Isso é ótimo para comandos e consultas, mas que tal eventos de domínio?

Eventos em aplicativos de negócios

Desde os primórdios da engenharia de software, os arquitetos têm desenvolvido aplicativos de linha de negócios capazes de sobreviver às mudanças nos negócios e acompanhar as alterações. Para oferecer suporte à business intelligence e análise estatística, os arquitetos também tem gerenciado para tornar as sequências de ações repetidas até certo ponto. Eles não as chamam “eventos de domínio” ou inventam termos legais, como “a origem de eventos” imediatamente, embora o conceito de eventos de domínio tenha sido usado amplamente por anos em sistemas de negócios. Isso é especialmente verdadeiro para sistemas de contabilidade, finanças e de serviços bancários.

Em seguida, tivemos a revolução do Domain Driven Design (DDD - Design controlado por domínio) e perdidos nesse “novo” modelo de software universal, acho que todos perderam alguns pontos de vista sobre a arquitetura de software. Os desenvolvedores, essencialmente concentrados em camadas e desconsiderando segmentos verticais de sistemas como pilhas de comandos e consultas.

Ao meu ver, a diferença entre o princípio formalizado do CQS pelo Bertrand Meyer e o princípios do CQRS de hoje está em tudo onde ela é aplicada. O CQS é mais um princípio universal para o desenvolvimento de software. O CQRS aborda a arquitetura do sistema. Quando você aplica CQS no nível de arquitetura, você tem o CQRS.

Primeiro, apresentei a versão inicial do CQRS no artigo, “CQRS para o aplicativo comum” (msdn.microsoft.com/magazine/mt147237). Aprofundei um pouco mais no artigo subsequente, “CQRS e aplicativos com base em mensagem” (msdn.microsoft.com/magazine/mt238399).

Esses artigos descrevem a pilha de comando com base em mensagens trocadas entre a apresentação e a lógica de negócios por meio da camada de aplicativo. Falei, de forma resumida, sobre eventos de negócios que você pode salvar para um armazenamento ad hoc de itens de negócios relevantes, como um pedido ou cancelamento de produto.

O foco era mais sobre o uso de mensagens para implementar fluxos de trabalho comerciais disparados por comandos. Nesse contexto, os eventos foram manipulados apenas entre notificações de ocorrências recentes às quais manipuladores talvez precisassem reagir. Essa é a primeira etapa de uma longa evolução voltada para fazer a transição de arquitetos de software da ideia de “modelos-para-persistir” para a ideia de “eventos-para registrar”. Consultei o CQRS como ponto de partida de uma mudança que terá um profundo impacto na arquitetura do sistema.

A maçã de Sir Isaac Newton e eu

Você já ouviu a história da maçã que caiu na cabeça de Sir Isaac Newton que levou-o a formular Lei Universal da Gravidade. É mais provavelmente uma lenda do que um fato, mas a verdade é que várias descobertas começam com um evento curioso. Recentemente, tive minha própria versão de uma maçã caindo sobre a minha cabeça que me fez pensar seriamente sobre o CQRS e eventos. Eu estava fazendo alguma análise preliminar para a recriação de um sistema que o cliente usava havia cerca de cinco anos.

O sistema do cliente era um site da WEB em ASP.NET MVC 2 com um banco de dados SQL Server sem formatação como back-end. A camada de acesso aos dados foi centralizada em um banco de dados relacional consumido através do Entity Framework. As classes de entidade inferidas foram estendidas com métodos adicionais, específicos da empresa usando o mecanismo de classe parcial do Microsoft .NET Framework. O comportamento entre as entidades foi delegado aos serviços de domínio. Embora não fosse possível chamá-lo de uma implementação de referência padrão de modelo de domínio, ele tinha pedaços e partes de fatos DDD aqui e ali. Em geral, poderia chamá-lo de um sistema Create, Read, Update, Delete (CRUD) simples, com alguma quantidade da lógica de negócios para ficar como um buffer entre o back-end e o restante do sistema.

Em software, um sistema CRUD é um aplicativo criado em torno de várias tabelas de banco de dados. As camadas que envolvem o banco de dados validam e estabelecem a base para as principais operações CRUD em relação ao banco de dados. Esse era o sistema.

Outro aspecto de um sistema CRUD que muitas vezes passa despercebido ou simplesmente não obtém a devida atenção, é que ele usa o banco de dados como um instantâneo. A qualquer momento, o banco de dados por trás de um sistema CRUD armazena o último estado conhecido do sistema. Às vezes, isso é suficiente, às vezes, não.

A maçã que caiu na minha cabeça me fez perceber que uma abordagem de banco de dados do instantâneo era correta para a versão um. Com a versão dois, o cliente queria mais. Agora o cliente gostaria de ser capaz de acompanhar o histórico e repetir sequências de etapas em condições diferentes para determinar o resultado. Quando se chega a isso, o banco de dados de instantâneo como a única forma (ou principal) de armazenamento, não é mais apropriado.

Um exemplo de aplicativo de reserva

Considere um aplicativo de permite aos usuários reservar uma sala de reuniões. Em algum momento, a camada de apresentação exibirá uma tela semelhante a da Figura 1.

Um modelo para o quadro de horários de salas
Figura 1 Um modelo para o quadro de horários de salas

Imagine que cada sala tem suas próprias regras de negócios sobre reservas. Por exemplo, os usuários podem agendar a sala Branca por uma hora das 8 às 10 e, depois, das 13 em diante. A sala Azul tem espaços de tempo de 30 minutos disponíveis a partir das 09:30 na parte da manhã. Finalmente, a sala Verde está disponível a partir das 9h00 em incrementos de uma hora. Esses detalhes são salvos e coincidem com as reservas existentes. Você pode gerar uma grade onde você pode esmaecer os espaços já em uso e tornar os espaços disponíveis capazes de responderem ao clique.

As regras que regem a disponibilidade da sala não estão definidas em absoluto e podem, de fato, ser alteradas. Sem segredos, você diz. Se houver mudanças, deixe que o administrador edite as regras de acordo. Essa abordagem funciona muito bem desde que você esteja usando o modelo da Figura 1para representar o estado do sistema mais recente.

No momento em que o cliente solicita para poder navegar entre a grade atual e a grade de três semanas antes, você está perdido. Mais precisamente, você está perdido se ocorrerem as alterações para as regras de negócio de reserva nas últimas três semanas. Você sabe os espaços e horários atuais para cada sala, mas tendo perdido os detalhes de como eram há três semanas.

Quando o cliente levantou isso, pensei que corrigi-lo era simplesmente uma questão de colocar algumas informações ausentes na tabela. Tudo o que precisava fazer era salvar algumas regras e retorná-las quando necessário. Tão simples quanto pode parecer, o acesso correto a uma pequena parte de informações perdidas requer uma alteração estrutural profunda. É como a ponta do iceberg e o iceberg é alternar de um banco de dados de instantâneo para representação de dados com base em eventos.

A origem de eventos se refere à representação de dados com base em eventos. Não abordarei a origem de eventos de forma detalhada aqui, mas esse é precisamente o tópico para a próxima coluna. No restante deste artigo, falarei sobre uma abordagem mista que combina instantâneos de banco de dados e logs de eventos de negócios. Provavelmente é uma boa primeira etapa para fazer a transição de arquiteturas clássicas existentes para o futuro. Mais do que qualquer nova e sofisticada tecnologia, estrutura, versão e tempo de execução, o grande negócio dos próximos anos é essencialmente em respeito a arquitetura. Dados baseados em eventos como origem de dados principais, em vez de instantâneos de dados (como mostrado na Figura 2).

A parte submersa do iceberg de eventos
Figura 2 A parte submersa do iceberg de eventos

Além de instantâneos de dados

Há décadas, a maioria de nós criou sistemas preocupado apenas com o estado atual do sistema mais recente. Nesse cenário, bancos de dados armazenavam instantâneos de entidades (ou agregavam, se você se sentir mais confortável com a terminologia DDD), e ela funcionava perfeitamente. Se você nunca sentiu a necessidade de registrar eventos de negócios na medida que eles ocorriam, então você provavelmente não precisava.

No momento em que o cliente solicita um recurso para o qual o relógio do aplicativo deve ser recuado para uma hora específica, no entanto, você está perdido. A única saída é refazer a arquitetura do sistema para salvar os eventos de negócio. Suponha agora que você tem uma tabela semelhante a da Figura 3.

Uma tabela de exemplo de armazenamento de regras de reserva para salas
Figura 3 Uma tabela de exemplo de armazenamento de regras de reserva para salas

Esta tabela indica que é possível reservar a sala 1 de acordo com as regras diferentes entre 1º de março e 1º de junho. Isso também significa que você deve recriar a grade da Figura 1 de acordo com a data à qual ela se refere.

O registro na tabela RoomRules é logicamente equivalente a um evento de negócios, porque ele indica que ocorreu um evento para alterar a regra de reserva. Em termos de implementação, no entanto, ele é um registro simples em uma tabela adicional e requer um monte de consultas extras (uma por sala) antes de preencher a grade da Figura 1.

Uma nova regra de reserva requer apenas um novo registro na tabela. Desenvolvedores cuidadosos, no entanto, provavelmente já teriam algum tipo de tabela de regras. Elas poderiam fornecer uma linha por sala. Nesse contexto, a única diferença parece ser uma coluna extra chamada ValidSince na Figura 3.

Vindo de uma visão de armazenamento de instantâneo clássica, o uso da palavra “evento de negócios” para definir um monte de registros adicionais em uma tabela adicional pode parecer um exagero. Ainda que isso seja precisamente a perspectiva que você tem quando você está direto no limite com os instantâneos acima de você e o mundo inteiro da origem de eventos está justamente abaixo.

Em termos mais específicos, as regras para a reserva de salas são eventos e registrá-los é controlar o histórico do sistema. Obter as regras que se aplicam a uma determinada sala é “reproduzir” os eventos para aquela sala que ocorreram desde a inicialização do sistema e até um determinado ponto. Talvez você não tenha o status atual salvo em algum lugar, mas tem todos os eventos registrados em log. Para obter o estado do sistema, basta repetir os eventos e criar uma instância válida da entidade Sala.

Isso parece estranho? Isso é como qualquer software bancário calcula seu “saldo” atual. O saldo nunca vem de uma leitura simples. Provavelmente, ele é um instantâneo persistido para uma determinada data e reproduzido em todos os eventos subsequentes.

Eventos e CQRS juntos

Os eventos fortalecem o software além da imaginação Mesmo os sistemas CRUD simples são alcançados assim que os clientes solicitam formas simples de business intelligence e análise estatística. Os eventos são imutáveis. Desse modo, você pode facilmente replicar bancos de dados de evento para novas instâncias de software. Essa é uma instrução chave da escalabilidade. Ao mesmo tempo, ter comandos e consultas separados (e portanto, pilhas distintas para processar comandos e executar consultas) permite concentrar-se em eventos na pilha de comando e ter tabelas de instantâneo prontas para leituras rápidas na pilha de consulta.

Se você tiver problemas de desempenho, você deve salvar os eventos ao executar um comando e atualizar todas as projeções de dados necessários para consultas. Por exemplo, quando você adiciona uma nova regra de reserva, de agora em diante, esse é o padrão. Você pode atualizar uma tabela de configurações atuais adicionais que podem ser consultadas rapidamente sem a reprodução de eventos. Em seguida, você pode voltar e repetir eventos conforme necessário.

Conclusão

Com eventos, você não perde nada. Você ainda terá seu negócio em execução e começará a aprender por si próprio um tipo de arquitetura mais poderosa. Armazenando dados como eventos, você afasta-se do nível da abstração de dados, ou seja, você pode usar a mesma quantidade de dados de baixo nível para criar qualquer número de instantâneos e projeções para vários objetivos de negócios, como consultas simples, business intelligence, análise hipotética, estatísticas, análises de uso e qualquer outra coisa que seja possível imaginar.


Dino Esposito é o co-autor do “Microsoft .NET: Architecting Applications for the Enterprise” (Microsoft Press, 2014) e “Programming ASP.NET MVC 5” (Microsoft Press, 2014). Um evangelista técnico para as plataformas Microsoft .NET Framework e Android na JetBrains e palestrante frequente em eventos do setor em todo mundo, Esposito compartilha sua visão do software em software2cents.wordpress.com e no Twitter em twitter.com/despos.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Jon Arne Saeteras