Ponta

CQRS para o aplicativo comum

Dino Esposito

Dino EspositoO design orientado a domínio (DDD) surgiu aproximadamente há uma década atrás, inspirando arquitetos e desenvolvedores de software. Além de seus méritos concretos e falhas, o DDD materializa um sonho antigo para que qualquer pessoa, desde os primórdios do paradigma orientado a objeto e pode estar relacionada: o sonho de criação de aplicativos em um modelo de objeto abrangente para lidar com todos os requisitos e preocupações dos participantes.

Na última década, muitos desenvolvedores embarcaram em projetos de seguir as diretrizes DDD. Alguns projetos foram bem-sucedidos, outros não. A verdade é que um modelo de objeto abrangente para cobrir todos os aspectos funcionais ou não-funcionais de um sistema de software é um utopia maravilhosa. Especialmente nos tempos loucos da experiência de usuário avançada, nos quais os modelos de negócio são alterados com frequência e os requisitos superados, um modelo de objeto sólida e estável é praticamente uma ilusão.

Recentemente, o outro método com outro acrônimo iniciado tem obtido força: Command and Query Responsibility Segregation (Segregação de responsabilidade de Comando e Consulta - CQRS). O CQRS não é o mais recente brinquedo legal de especialistas de software. Ele nem é mesmo tão complexo quanto implicam exemplos mais disponíveis. Em poucas palavras, o CQRS é um padrão de implementação concreto que é provavelmente mais apropriado para praticamente qualquer tipo de aplicativo de software, independentemente do tempo de vida esperado e a complexidade.

Não há apenas uma maneira de fazer o CQRS — há pelo menos três sabores diferentes. Você ainda poderia apelidá-los após as regras comuns de marketing de quartos de hotel e bebidas: regular, premium e deluxe. Fazer uma pesquisa rápida para obter exemplos e artigos de CQRS e a maioria dos quais você encontrará cairá na categoria deluxe. Isso é efetivamente muito complexo e exagerado para aplicativos mais comuns.

O CQRS é uma abordagem ao desenvolvimento de software que compensa, independentemente da complexidade do projeto. No final do dia, o CQRS para o aplicativo comum é simplesmente uma reformulação da arquitetura multicamada clássica que deixa a porta aberta para alterações com mais impacto e evolução.

Comandos e consultas

Ao desenvolver a linguagem de programação Eiffel nos anos 80, Bertrand Meyer veio à conclusão de que o software tem comandos que alteram o estado do sistema e as consultas que leem o estado do sistema. Qualquer instrução de software deve ser um comando ou uma consulta — não uma combinação dos dois. Há uma maneira melhor de expressar o mesmo conceito: Uma pergunta não deve alterar a resposta. O CQRS é uma reformulação mais recente do mesmo princípio principal: Comandos e consultas são coisas diferentes e devem ser implementadas separadamente.

A separação lógica entre comandos e consultas não aparece muito bem se os dois grupos de ações são forçados a usar a mesma pilha de programação e o mesmo modelo. Isso é especialmente verdadeiro em cenários de negócios complexos. Um único modelo — quer um modelo de objeto modelo funcional ou qualquer outra coisa — pode rapidamente se tornar impossível de gerenciar. O modelo torna-se exponencialmente grande e complexo e absorve tempo e dinheiro, mas nunca funciona como deveria.

A separação tratada pelo CQRS é obtida pelo agrupamento de operações de consulta em uma camada e comandos em outra camada. Cada camada tem seu próprio modelo de dados, seu próprio conjunto de serviços e é compilada usando sua própria combinação de tecnologias e padrões. Mais importante, as duas camadas podem até ser em dois níveis distintos e ser otimizadas separadamente sem afetar a outra. A Figura 1prepara a base para a arquitetura CQRS.

Uma arquitetura CQRS canônica e multicamadas
Figura 1 Uma arquitetura CQRS canônica e multicamadas

Apenas reconhecer que comandos e consultas são duas coisas diferentes tem um impacto profundo na arquitetura de software. Por exemplo, de repente ficou mais fácil de prever e codificar cada camada de domínio. A camada de domínio na pilha de comando diz respeito apenas às regras de dados e comerciais e de segurança necessárias para realizar tarefas. A camada de domínio na pilha de consulta, por outro lado, pode ser tão simples quanto uma consulta SQL direta através de uma conexão ADO.NET.

Quando você coloca as verificações de segurança na porta de apresentação, a pilha de consulta se torna um wrapper estreito ao redor do Entity Framework ou seja lá o você pode usar para consultar dados. Dentro de cada camada de domínio, você também tem a liberdade para dados de forma bastante semelhante com as necessidades de domínio sem duplicar ou replicar dados para acomodar as diversas necessidades de negócios e apresentação.

Quando o DDD apareceu pela primeira vez, era sobre como lidar com a complexidade no Centro de desenvolvimento de software. No entanto, ao longo do caminho, profissionais enfrentaram cargas de complexidade. Muitos pensaram que seria simplesmente parte do domínio de negócios. Em vez disso, a maior parte dessa complexidade era resultante do produto cartesiano de consultas e comandos. Separar comandos de consultas reduz a complexidade em uma ordem de magnitude. Em termos de matemática aproximada, você pode comparar o CQRS e uma abordagem de modelo de domínio abrangente em termos de N+N em comparação com NxN.

Como começar a fazer o CQRS?

Você pode mudar um sistema básico create, read, update, delete (criar, ler, atualizar e excluir - CRUD) em um sistema inspirado no CQRS. Suponha que você tenha um aplicativo Web ASP.NET MVC canônico que coleta dados de usuário a ser exibido em várias formas. Isso é o que a maioria dos aplicativos faz e o que um arquiteto sabe como criar com rapidez e eficiência. Você poderá reescrevê-lo com o CQRS em mente. Você ficará surpreso ao ver que as alterações necessárias são mínimas, mas os benefícios que você pode obter são potencialmente ilimitados.

O sistema canônico é organizado em camadas. Você tem serviços de aplicativos chamados diretamente de controladores que organizam casos de uso. Os serviços de aplicativo (também conhecidos como serviços de trabalho) ao vivo no servidor Web lado a lado com os controladores. Com relação a Figura 1, os serviços de aplicativo formam a camada de aplicativo. A camada de aplicativo é a plataforma da qual você executar consultas e comandos contra o restante do sistema. Aplicar o CQRS significa que você usará duas camadas intermediária distintas. Uma camada cuida de comandos que alteram o estado do sistema. A outra recupera os dados. A Figura 2 mostra o diagrama de arquitetura em um projeto ASP.NET MVC de exemplo.

A arquitetura CQRS para um projeto ASP.NET MVC
Figura 2 A arquitetura CQRS para um projeto ASP.NET MVC

Crie alguns projetos de biblioteca de classes, pilha de consulta e pilha de comando, e faça referência aos dois do projeto de servidor da Web principal.

A pilha de consulta

A biblioteca de classes de pilha de consulta só está preocupada com a recuperação de dados. Ela usa um modelo de dados que corresponde aos dados usados na camada de apresentação o mais próximo possível. Você raramente precisa quaisquer regras de negócios aqui, pois elas se aplicam aos comandos que alteram o estado.

O padrão de modelo de domínio popularizado pelo DDD é essencialmente uma forma de organizar a lógica do domínio. Quando você faz consultas de front-end, você está lidando apenas com parte da lógica do aplicativo e casos de uso. A lógica de negócios do termo geralmente resulta da união de lógica específica do aplicativo com a lógica invariável de domínio. Se você souber o formato de persistência e o formato de apresentação, basta mapear os dados como faria em uma consulta do bom e velho ADO.NET/SQL.

É útil se lembrar de que qualquer código que você pode chamar da camada de aplicativo representa o domínio de negócios do sistema. Portanto, é a API invariável que expressa a lógica principal do sistema. Em condições ideais, você deve garantir que nenhuma operação inconsistente e incongruente seja possível mesmo por meio da API exposta. Então para impor a natureza somente leitura da pilha de consulta, adicione uma classe wrapper em torno do objeto de contexto do Entity Framework padrão para se conectar ao banco de dados, conforme mostrado no código a seguir:

public class Database : IDisposable
{
  private readonly QueryDbContext _context = new QueryDbContext();
  public IQueryable<Customer> Customers
  {
    get { return _context.Customers; }
  }
  public void Dispose()
  {
   _context.Dispose();
  }
}

A classe Matches é implementada como uma coleção DbSet<T> na classe base DbContext. Como tal, ela fornece acesso completo ao banco de dados subjacente e você pode usá-la para configurar consultas e atualizar operações por meio de LINQ às Entidades.

A etapa fundamental para configurar um pipeline de consulta é permitir o acesso ao banco de dados somente para consultas. Essa é a função da classe wrapper, onde Matches é exposta como um IQueryable<T>. A camada de aplicativo usará a classe de wrapper do banco de dados para implementar consultas direcionadas em trazer os dados para a apresentação:

var model = new RegisterViewModel();
using (var db = new Database())
{
  var list = (from m in db.Customers select m).ToList();
  model.ExistingCustomers = list;
}

Agora há uma conexão direta entre a fonte de dados e a apresentação. Você está apenas lendo e formatando os dados para fins de exibição. Você espera que a autorização seja imposta na porta por meio de logons e restrições de interface do usuário. Caso contrário, você pode adicionar mais camadas ao longo do processo e habilitar a troca de dados por meio de coleções de dados IQueryable. O modelo de dados é o mesmo que o banco de dados e é de 1 para 1 com persistência. Esse modelo é às vezes chamado de árvores de expressão em camadas (LET).

Há algumas coisas que você deve observar neste momento. Primeiro, você está no pipeline de leitura em que as regras de negócios normalmente não existem. Tudo o que você pode ter aqui são filtros e regras de autorização. Essas são bem-conhecidas no nível de camada de aplicativo. Você não precisa lidar com objetos de transferência de dados ao longo do caminho. Você tem um modelo de persistência e contêineres de dados reais para o modo de exibição. No serviço de aplicativo, você acaba com o seguinte padrão:

var model = SpecificUseCaseViewModel();
model.SomeCollection = new Database()
     .SomeQueryableCollection
     .Where(m => SomeCondition1)
     .Where(m => SomeCondition2)
     .Where(m => SomeCondition3)
     .Select(m => new SpecificUseCaseDto
       {
         // Fill up
       })
     .ToList();
return model;

Todos os objetos de transferência de dados especiais no trecho de código são específicos para o caso de uso de apresentação que você está implementando. Elas são apenas o que o usuário deseja ver no modo de exibição Razor, que você está criando e as classes são inevitáveis. Além disso, você pode substituir todas as cláusulas Where com métodos de extensão IQueryable ad hoc e ativar todo o código na caixa de diálogo escrita em linguagem específica do domínio.

A segunda coisa a observar sobre a pilha de consulta é relacionada a persistência. Na forma mais simples de CQRS, as pilhas de comando e consulta compartilham o mesmo banco de dados. Essa arquitetura torna o CQRS semelhante aos sistemas CRUD clássicos. Isso facilita a adotá-lo quando as pessoas são resistentes a alteração. No entanto, você pode criar o back-end para que as pilhas de comando e consulta tenham seus próprios bancos de dados otimizados para suas finalidades específicas. Sincronizar dois bancos de dados, em seguida, torna-se a outro problema.

A pilha de comando

No CQRS, a pilha de comando diz respeito apenas a execução de tarefas que modificam o estado do aplicativo. A camada de aplicativo recebe solicitações da apresentação e organiza a execução enviando comandos no pipeline. A expressão "enviar comandos ao pipeline" é a origem de vários tipos de CQRS.

No caso mais simples, enviar um comando consiste em simplesmente invocar um script de transação. Esse dispara um fluxo de trabalho simples que realiza todas as etapas exigidas pela tarefa. Enviar um comando da camada de aplicativo pode ser tão simples quanto o código a seguir:

public void Register(RegisterInputModel input)
{
  // Push a command through the stack
  using (var db = new CommandDbContext())
  {
    var c = new Customer {
      FirstName = input.FirstName,
      LastName = input.LastName };
    db.Customers.Add(c);
    db.SaveChanges();
  }
}

Se necessário, você pode transferir o controle para a camada de domínio verdadeiro com serviços e o modelo de domínio onde você implementa a lógica de negócios completa. No entanto, usar o CQRS não vincula você necessariamente ao DDD e coisas como agregações, fábricas e objetos de valor. Você pode ter os benefícios da separação de comando/consulta sem a complexidade extra de um modelo de domínio.

Além do CQRS Regular

O poder do CQRS reside no fato de que você pode otimizar pipelines de comando e consulta à vontade, sem o risco que a otimização de um pode danificar o outro. A forma mais básica de CQRS usa um banco de dados compartilhado e chama bibliotecas distintas para leituras e gravações da camada de aplicativo.

Formas mais sofisticadas podem ter vários bancos de dados, persistência de várias linguagens, desnormalização de dados para fins de consulta, fonte de eventos e, mais importante, uma maneira mais flexível de tratar os comandos para o back-end. Ela é mais flexível porque usando um barramento para enviar comandos e publicar eventos permite definir e modificar qualquer tarefa que ocorra de modo semelhante ao gerenciamento de um fluxograma. Ao mesmo tempo, você pode dimensionar virtualmente onde você precisa estar adicionando recursos e poder para o componente de barramento.

Muitos desenvolvedores elogiam o CQRS, mas tendem a limitar a aplicabilidade para aplicativos de colaboração e de grande escala. O CQRS não é a arquitetura de nível superior e é independente de tecnologia. Em um determinado nível, o CQRS é mesmo independente de padrões de design, mas é um padrão em si. É simples e poderoso e perfeito para aplicativos comuns.


Dino Esposito é o co-autor de “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 um 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