Com o crescimento exponencial da informação, tem-se ouvido muito falar em escalabilidade e disponibilidade. Como tornar uma aplicação totalmente escalável que funcione tão bem com um usuário quanto com ‘N’ usuários? Esta é uma tarefa muito complexa de ser realizada tanto de forma conceitual quanto prática. Considere um ambiente onde se tem consumo de uma mesma informação por muitos usuários, este conjunto de dados pode ser editado constantemente, pode ter acrescentadas novas informações a todo o momento e os usuários não estão dispostos a esperar os 'locks' da aplicação para poder consumir uma informação. Agora construa um sistema que consiga sobreviver num ambiente tão ágil e volátil. Para descrever de forma prática este tipo de ambiente, temos os ambientes de SAC das grandes empresas, os call centers tentando consultar informações a todo o momento, a área de cadastro atualizando as informações e o cliente querendo agilidade no seu atendimento.

Observando que um grupo limitado de informação é utilizado e alterado por uma grande quantidade de usuários, na constância que os dados são atualizados, é natural que após um usuário abrir alguma informação na tela a mesma possa estar sendo atualizada por outro agente, seja ele software ou humano, e a informação exibida pode já estar obsoleta. Numa abordagem pessimista, toda informação exibida na tela pode já estar desatualizada.

O CQRS parte deste ponto, uma vez que toda informação presente na visualização pelo usuário pode não ser a mais atual, então uma visualização não precisa ter sua performance afetada por uma alteração ou inclusão.

CQRS significa Command Query Responsibility Segregation e este modelo foi documentado por Greg Young enquanto implementava projetos utilizando as boas práticas do DDD (Domain Driven Design). O CQRS prega a divisão de uma mesma funcionalidade em responsabilidades entre comandos e consultas. Não somente uma segregação lógica, mas também física. Segundo este conceito todo comando é o que envolve inclusão ou alteração no estado de qualquer entidade e a consulta por sua vez apenas obtém o estado atual sem executar qualquer mudança. As consultas devem ser feitas de forma síncrona, em uma base desnormalizada e separada, assim obtendo maior performance e disponibilidade. Os comandos por sua vez, são assíncronos, imperativos e suas respostas são tratadas por eventos, conforme exibe a Figura 1.

Figura 1. Arquitetura CQRS

Esta abordagem não é um padrão arquitetural de alto nível, mas de uma abstração menor como componentes. É necessário avaliar caso a caso quanto às vantagens e desvantagens de se aplicar um modelo como este, pois muita complexidade seria agregada ao projeto. Não existe uma sequência de passos definida para o desenvolvimento deste conceito, mas o ideal para aplicá-lo é separar seu sistema em pequenos componentes que devem ser responsáveis somente por realizar uma tarefa em todo sistema. Se o componente necessitar ter alterações por mais de um motivo, é preciso revê-lo e até dividi-lo, pois há grandes chances de ele ter responsabilidades não pertinentes ao seu propósito.

Um bom exemplo prático sobre QCRS é o bloqueio de cadastro de um cliente quando um usuário consulta em uma tela os dados deste cliente. Esta resposta tem de ser rápida devida a grande quantidade de operações que o mesmo deve operar, não tem como ser uma tarefa assíncrona, pois o usuário precisa desta informação no momento da consulta. Supondo que a mesma tela de consulta possua um botão de bloqueio de cadastro, então ao se clicar neste botão, o usuário está solicitando o envio de um comando de alteração de estado na entidade cliente. O comando ‘BloquearCliente’ entrará numa fila de execução de comandos centralizada e única. Quando for executado, o mesmo alterará o estado do cliente para bloqueado na base normalizada e gerará um evento reportando o sucesso da execução como ‘ClienteBloqueado’, e gerará um outro evento de atualização na base desnormalizada, atualizando assim as informações que o usuário visualizará.

As atualizações a base desnormalizada são feitas de forma assíncrona, pois como toda informação contida lá é, de uma forma geral, uma situação passada, esta operação não precisa ser imediata. Porém, aqui se tem um perigo eminente: se a atualização e a visualização do usuário são feitas de forma assíncrona e totalmente separadas, pode ocorrer de enquanto um usuário envia um bloqueio de conta, outro esteja enviando uma aprovação de venda para o mesmo cliente?

A resposta é sim e não concomitantemente. porque eles vão poder enviar os dois comandos ao mesmo tempo, porém todos os comandos tem um túnel único de execução, o que vai permitir uma espécie de controle transacional. Ou seja, se o bloqueio for enviado antes da aprovação de venda, quando o segundo comando for executado o cliente já estará bloqueado e então um evento de erro será gerado como resposta a ação. Se o comando de venda for enviado antes ficará a critério da regra de negócio se o de bloqueio deve cancelar ou não as vendas em aberto.

A base desnormalizada x normalizada

A base de consulta de dados pode ser outro banco ou mesmo um cache, deve ser totalmente desnormalizada e específica para uma visualização (tela) do sistema. Ela será alterada pelos eventos pós a atualização da base normalizada. A desnormalização permiti maior performance nas consultas devido à ausência de ‘joins’, porém assim como num modelo dimensional ela irá possuir duplicidade de dados.

A base normalizada é a principal do sistema, ela é consumida também por outros, naturalmente. Os eventos publicados pela atualização da base normalizada podem ser assinados por um ou mais agentes de atualização, pois isso permite a atualização de múltiplas bases de consulta ao mesmo tempo, uma vez que estas são específicas para seus respectivos componentes.

Divisão no código

O modelo das entidades será dividido em dois, o Query Model, responsável pela consulta, assim como a base desnormalizada ele não respeita muito a programação orientada a objeto e é específico para a visualização que o utiliza, geralmente são DTOs, não podem possuir regras, cálculos ou validações. Seu processamento tem que ser o mínimo possível e sua resposta necessita ser rápida.

Command Model, aqui é onde se encontra o DDD, suas regras de negócio e validações e devem ser aplicadas todas as regras para que toda alteração de estado sobre as entidades seja válida. Como o processamento é assíncrono, para o usuário parecerá instantâneo. As exceções aqui são tratadas por eventos, num modelo publish/subcriber. Cada comando deve conter o mínimo possível de informação para funcionar, não é necessário enviar entidades inteiras dentro de um comando,pois isso geraria um consumo desnecessário de recursos e este não é o proposito deste modelo.

Este modelo de envio de comandos é uma abordagem behaivor-centric, onde o usuário insere um comando pela UI, a mesma encaminha o comando para um túnel de comandos que dever ser único. Este comando deve ser imperativo como “FinalizarCompra”.

O comando é processado de forma assíncrona e um evento é publicado com a resposta de sucesso ou falha da ação. Este tipo de abordagem permite aumentar a disponibilidade, desempenho e escalabilidade do sistema mesmo num ambiente completamente inconstante, pois remove os tempos de espera do usuário para aplicação de regras de negócio e enfileira as alterações e inclusões diminuindo o consumo de recursos de hardware. Pelo fato das consultas serem separas e específicas, seu tempo de processamento também é diminuído significantemente.

É normal que conforme o modelo de negócio atendido pelo domínio for aumentando, a complexidade natural do sistema vai aumentando e que o modelo original proposto da solução vá se expandindo e perdendo sua proposta inicial. Aplicando o CQRS, esta complexidade aumentará ainda mais, uma vez que uma mesma entidade vai passar a ter múltiplas representações conceituais.

Muito obrigado e até a próxima.