DEV Community

Cover image for Vertical Slice: Um Déjà Vu do CQRS

Vertical Slice: Um Déjà Vu do CQRS

No cenário em constante evolução da arquitetura de software, certos padrões emergem para enfrentar desafios específicos na construção de aplicações sustentáveis, escaláveis e eficientes. Dois desses padrões são Command Query Responsibility Segregation (CQRS) e Vertical Slice Architecture. Embora o CQRS tenha sido uma abordagem de design proeminente por vários anos, a Vertical Slice Architecture, popularizada por Jimmy Bogard, ganhou força por sua metodologia pragmática e focada em capacidades de negócio e funcionalidades de negócio.

Este post traça um paralelo entre a história e as motivações por trás do CQRS, explora o conceito de arquitetura Vertical Slice e compara esses dois padrões usando exemplos de código C# e organização de soluções do Visual Studio.

A origem e motivações por trás do CQRS

O Surgimento do CQRS

Fundamentado no CQS, criado por Bertrand Meyer, Command Query Responsibility Segregation (CQRS) é um padrão arquitetural introduzido por Greg Young por volta de 2010. O CQRS foi concebido para resolver vários problemas inerentes às arquiteturas tradicionalmente monolíticas e em camadas, onde as várias operações estavam implementadas em uma única base de código e muitas vezes uma única classe continha várias operações de leitura e escrita aninhadas, fornecendo vários tipos de operações sem separação de responsabilidades.

As arquiteturas tradicionais geralmente combinam operações de leitura e gravação na mesma base de código e modelos de dados, levando a uma lógica que, muitas vezes, pode se tornar complexa de manter e também muito acoplada, tornando difícil a manutenção e evolução do sistema.

Problemas resolvidos pelo CQRS

  1. Separação de preocupações: Ao segregar comandos (escrita) de consultas (leituras), o CQRS promove uma separação clara de responsabilidades. Essa separação permite que os desenvolvedores otimizem e dimensionem as operações de leitura e gravação de forma independente, resultando em um código mais eficiente e de fácil manutenção.
  2. Otimização de desempenho: Em muitas aplicações, as operações de leitura giram em torno de 95% das tarefas executadas por um sistema, e apenas 5% do restante das operações são de gravação. O CQRS permite otimizar modelos de leitura separadamente dos modelos de gravação, muitas vezes levando a ganhos significativos de desempenho.
  3. Escalabilidade: Com o CQRS, diferentes partes do sistema podem ser dimensionadas de forma independente. Por exemplo, um aplicativo com muita leitura pode expandir seus componentes de leitura sem afetar o lado de gravação.
  4. Gerenciamento de complexidade: o CQRS facilita o gerenciamento de validações e lógicas de negócios complexas no lado da gravação, mantendo o lado da leitura simples e otimizado para consultas.

⚠️ Não é uma regra ter que separar as instâncias de sua aplicação em uma instância de leitura e uma outra de escrita (gravação). essa abordagem pode acontecer, mas apenas em casos em que a performance do sistema demande essa separação física dos componentes. Aplicar CQRS em sua essência e em seu último nível de implementação arquitetural, vai introduzir um alto grau de complexidade para dentro da solução da sua arquitetura. Por isso, não seja purista aplicando 100% do que você leu e aprendeu sobre esse padrão arquitetural, analise e pense qual é o nível de abstração e implementação que o seu sistema demanda do padrão CQRS!

Sistemas CRUD-Based ofuscam a modelagem de um sistema

Existem muitos sistemas que ainda estão sendo construídos com base no fundamento de servir aos clientes (eg.: Frontend), apenas o DTO que é usado no lado servidor, afim de simplificar o processo de desenvolvimento, buscando acelerar a entrega de funcionalidades no mercado.

Esse tipo de abordagem, conduz não só os desenvolvedores do sistema, mas também implica, negativamente, no desenho das telas, levando a uma UI/UX a terem funcionamento atômico, onde o comportamento das funcionalidades se resumem à apenas uma ação que estimule um sistema a recolher todas as alterações feitas na tela (UI/UX), envie para o servidor o DTO alterado, implicando, no final do processamento, persistir esses dados alterados em alguma base de dados.

Através dessa abordagem, muitos pontos de design - não apenas de UI/UX, como também de software e modelagem de negócio, ficaram perdidos ao longo de todo o processo de desenvolvimento, testes e definição de negócio. Uma importante definição de negócio, a qual deveria levar em consideração: as intenções, nesse modelo de desenvolvimento são facilmente deixadas de lado. Se fossem melhor melhor exploradas, poderiam resultar delimitação do escopo das ações em um formato mais bem definido, tornando, inclusive, mais fácil a implementação de sistemas ou parte de sistemas que se baseiam em CRUD.

Abaixo tem uma tela de um perfil de usuário, como exemplo:

CRUD-Based

Através dessa imagem, é fácil notar que a intenção dessa tela é gerir, de uma maneira geral, o perfil de um usuário. Tornando difícil de descobrir qual foi a real intenção que um usuário teve, ao clicar no botão salvar. Isso porque o frontend vai capturar todos os dados que existirem na tela e vai enviar, de volta para o servidor. Com isso o rastreamento da operação fica ofuscado pela modelagem CRUD-Based.

Interface de usuário baseada em tarefas

A ideia básica por trás de uma UI baseada em tarefas ou indutiva, é descobrir como os usuários desejam usar o software e fazer com que ele os oriente nesses processos. O objetivo é orientar o usuário durante o processo.

Um software desenhado com base em uma interface baseada em tarefas adotaria uma abordagem diferente, provavelmente separaria as ações de gestão de dados pessoais, endereço e dados de cobrança em seções diferentes, onde cada uma delas pudessem demonstrar a intenção sobre a ação desejada. A intenção do usuário é clara neste caso e o software está guiando os seus passos através do processo de gestão de cada parte da informação de forma isolada, porém contextualizada dentro do perfil do usuário. Dessa forma, as ações podem ser usadas para a criação de comandos que representam as intenções do usuário com este estilo de interface.

Task-based
A tela acima contém uma ação para gestão de dados pessoais!

Task-based
A tela acima contém uma ação para gestão de dados de cobrança!

Capacidades de negócio + Funcionalidades de negócio:

💡 Todo sistema é construído com base em uma necessidade de negócio, a qual demanda que esse produto seja orientado às capacidades de negócio destinadas a resolver um problema específico de um dado plano de negócio, em um dado nicho de mercado, e também aborda quais serão as funcionalidades de negócio definidas para cada uma dessas capacidades.

Um sistema de Consultas Veterinárias, pode ter como capacidades de negócio:

  • Agendamento de consulta
  • Histórico médico
  • Acompanhamento de tratamentos
  • Cobrança

E cada uma dessas capacidades pode conter diversas funcionalidades, como por exemplo:

  • Histórico médico
    • Revisar o histórico do paciente
    • Atualizar o histórico do paciente
  • Agendamento de consulta
    • Procurar veterinário disponível
    • Agendar consulta

Implementação básica de CQRS

Vamos voltar um pouco ao contexto do exemplo das imagens acima. Em uma implementação típica de CQRS, comandos são usados para realizar operações de gravação e consultas são usadas para recuperar dados que representam o estado atual de uma parte do sistema. Aqui está um exemplo simples em C#:

Exemplo de um Comando (Command):

public class AtualizarDadosPessoaisCommand
{
    public int ClienteId { get; set; }
    public string Nome { get; set; }
    public DateTime DataNascimento { get; set; }
}

public class AtualizarDadosPessoaisCommandHandler
{
    private readonly IClienteRepositorio _clienteRepositorio;

    public AtualizarDadosPessoaisCommandHandler(IClienteRepositorio clienteRepositorio)
    {
        _clienteRepositorio = clienteRepositorio;
    }

    public void Handle(AtualizarDadosPessoaisCommand command)
    {
        var dadosPessoais = new ClienteDadosPessoais
        {
            ClienteId = command.ClienteId,
            Nome = command.Nome,
            DataNascimento = command.DataNascimento
        };

        _clienteRepositorio.AtualizarDadosPessoais(dadosPessoais);
    }
}
Enter fullscreen mode Exit fullscreen mode

Exemplo de uma Consulta (Query):

public class ObterClientePorIdQuery
{
    public int ClienteId { get; set; }
}

public class ObterClientePorIdQueryHandler
{
    private readonly IClienteRepositorio _clienteRepositorio;

    public ObterClientePorIdQueryHandler(IClienteRepositorio clienteRepositorio)
    {
        _clienteRepositorio = clienteRepositorio;
    }

    public Cliente Handle(ObterClientePorIdQuery query)
    {
        return _clienteRepositorio.ObterPorId(query.ClienteId);
    }
}
Enter fullscreen mode Exit fullscreen mode

Uma solução arquitetural que adota os conceitos CQRS, normalmente carrega como base de definição uma organização de projeto seguindo capacidades de negócio, detalhando as funcionalidades intrínssicas dentro de cada uma dessas capacidades. Abaixo, deixo um exemplo de organização de solução onde está explicitamente citada a capacidade de gestão de perfil de cliente (usuário) e quais são as funcionalidades que compõe essa capacidade. Essa abordagem permite que um projeto de software seja fácil de ser compreendido tanto do ponto de vista sobre QUE PROBLEMA ele está resolvendo e COMO está organizada a base de código dessa solução, além, também, de facilitar a compreensão sobre que tipo de padrão está sendo adotado em sua implementação.

.sln (solution)
├── Capacidades-de-negocio
│   ├── PerfilCliente
│   │   ├── AtualizarDadosPessoais
│   │   │   ├── AtualizarDadosPessoaisCommand.cs
│   │   │   ├── AtualizarDadosPessoaisCommandHandler.cs
│   │   │   └── AtualizarDadosPessoaisResponse.cs
│   │   ├── AtualizarDadosCobranca
│   │   │   ├── AtualizarDadosCobrancaCommand.cs
│   │   │   ├── AtualizarDadosCobrancaCommandHandler.cs
│   │   │   └── AtualizarDadosCobrancaResponse.cs
│   │   ├── ObterClientePorId
│   │   │   ├── ObterClientePorIdQuery.cs
│   │   │   ├── ObterClientePorIdQueryHandler.cs
│   │   │   └── ObterClientePorIdResponse.cs
│   ├── ...
Enter fullscreen mode Exit fullscreen mode

Introdução à arquitetura Vertical Slice

O que é esse tipo de arquitetura?

A Arquitetura Vertical Slice, defendida por Jimmy Bogard, é um padrão que estrutura o código por capacidades de negócio e suas funcionalidades intrínssicas e não com base em CRUD-based. Essa abordagem promove o encapsulamento de todos os aspectos de um recurso (UI, lógica de negócios e acesso a dados) em uma única fatia vertical. Cada slice (fatia) é independente, tornando o sistema mais modular e mais fácil de manter.

Principais vantagens da arquitetura Vertical Slice

  1. Foco em capacidades e funcionalidades de negócio: a organização do código é definida pelas capacidades e funcionalidades de negócio, alinhada à forma como os usuários finais percebem o aplicativo, tornando-o mais intuitivo para os desenvolvedores trabalharem em funcionalidades específicas.
  2. Isolamento e Modularidade: Cada slice (fatia) é isolada umas das outras, reduzindo o risco de efeitos colaterais não intencionais ao modificar um recurso.
  3. Maior capacidade de manutenção: Unidades de código menores e independentes são mais fáceis de entender, testar e refatorar.
  4. Desenvolvimento Paralelo: As equipes podem trabalhar em diferentes fatias simultaneamente, sem atrapalhar umas às outras, acelerando o desenvolvimento.

Implementação do Vertical Slice

Na arquitetura Vertical Slice, cada recurso inclui tudo o que precisa para funcionar: comandos, consultas, manipuladores e, às vezes, até mesmo a camada de acesso ao banco de dados. A partir desse ponto, começamos a ter um Dèjá Vu sobre a perspectiva com a qual o Vertical Slice orienta a organização da solução, nos levando de volta à como o CQRS também organiza uma solução de projeto de software, baseando-se em capacidades e funcionalidades de negócio.

Reaproveitando o mesmo contexto de negócio que foi utilizado para exemplificar o CQRS, veja como você pode organizar uma solução em seu projeto de software, usando Vertical Slice:

.sln (solution)
├── Capacidades-de-negocio
│   ├── PerfilCliente
│   │   ├── AtualizarDadosPessoais
│   │   │   ├── AtualizarDadosPessoaisCommand.cs
│   │   │   ├── AtualizarDadosPessoaisCommandHandler.cs
│   │   │   └── AtualizarDadosPessoaisResponse.cs
│   │   ├── AtualizarDadosCobranca
│   │   │   ├── AtualizarDadosCobrancaCommand.cs
│   │   │   ├── AtualizarDadosCobrancaCommandHandler.cs
│   │   │   └── AtualizarDadosCobrancaResponse.cs
│   │   ├── ObterClientePorId
│   │   │   ├── ObterClientePorIdQuery.cs
│   │   │   ├── ObterClientePorIdQueryHandler.cs
│   │   │   └── ObterClientePorIdResponse.cs
│   ├── ...
Enter fullscreen mode Exit fullscreen mode

Comparação: CQRS vs. Vertical Slice

Semelhanças

  1. Separação de capacidades e funcionalidades de negócio: Tanto o CQRS quanto o Vertical Slice enfatizam a separação por capacidades e funcionalidades de negócio dentro de uma solução, resultando em um código mais limpo e de fácil manutenção.
  2. Modularidade: Cada abordagem promove a modularidade, facilitando o dimensionamento e a modificação de partes do sistema de forma independente.
  3. Foco em tarefas: A arquitetura Vertical Slice, assim como o CQRS, é orientada pela implementação específica de tarefas, garantindo que o código seja organizado em torno das funcionalidades de negócios.

Diferença

Gerenciamento de Complexidade: Dependendo do grau de demanda de escalabilidade de um sistema, o que pode levar a implementação do último nível do CQRS, pode introduzir complexidade adicional devido à abordagem de separação das instâncias de escrita e leitura, implicando em uma solução que precisaria lidar com consistência eventual. O Vertical Slice visa reduzir a complexidade, mantendo o código relacionado junto na mesma fatia.

Conclusão

Tanto o CQRS quanto a arquitetura Vertical Slice oferecem paradigmas valiosos para estruturar aplicações modernas. O CQRS é excelente em cenários onde as operações de leitura e gravação precisam ser otimizadas de forma independente e podem lidar com lógica de negócios complexa de forma eficaz. A Arquitetura Vertical Slice, por outro lado, fornece uma abordagem mais intuitiva e de fácil manutenção, organizando o código em torno de recursos, facilitando o desenvolvimento e o gerenciamento.

Ao compreender os pontos fortes e as nuances de cada padrão, os desenvolvedores podem escolher a arquitetura mais apropriada para suas necessidades específicas, resultando em aplicativos mais robustos e de fácil manutenção. Quer você opte pela separação estrita de preocupações oferecida pelo CQRS ou pela modularidade focada em recursos do Vertical Slice, ambos os padrões fornecem ferramentas poderosas para o desenvolvimento de software moderno.

Referências

Top comments (0)