DEV Community

Cover image for CQS Pattern - Quando, como e por quê utilizar?
Caio Flavio
Caio Flavio

Posted on • Edited on

CQS Pattern - Quando, como e por quê utilizar?

Introdução

Este post tem como objetivo fazer um resumo sobre o que aprendi sobre Command Query Separation (CQS) e compartilhar com outras pessoas que estejam pesquisando sobre esse assunto.
Eu particularmente tenho uma certa dificuldade em definir a responsabilidade de cada uma da minhas classes quando estou levando em consideração o Principio da Responsabilidade Única e o que tem me ajudado a organizar melhor essas responsabilidades é entender como outros programadores mais experientes fazem isso. E durante uma sessão de estudos fui apresentado ao Command Query Segregation (CQS) um pattern que foi introduzido por Bertrand Meyer que tem como princípio que todo método deve executar uma ação ou retornar dados, mas nunca os dois ao mesmo tempo. É importante frisar que como todo design pattern ele não é uma bala de prata que vai resolver todos os nossos problemas e por isso iremos discutir um pouco sobre como e quando podemos aplicar esse padrão de desenvolvimento.

Wanted bullet shot film gif

Definições

O autor definiu que todo método deve executar uma ação ou retornar dados, mas nunca os dois aos mesmo tempo. Nesse momento você pode estar pesando: "mas retornar dados não é uma ação?". Quando falamos em CQS ações ou Commands são operações que possuem efeitos colaterais, ou seja, alteram o estado de uma variável, um registro no banco, etc... quando há alguma alteração envolvida estamos estamos executando uma ação ou Command. E quando estamos apenas consultando um dado de onde quer que seja sem causar efeitos colaterais no sistema estamos executando uma consulta ou Query.

Casos de uso

É sempre bom lembrar que o uso de CQS é uma escolha de arquitetura e não quer dizer que realizar ação e retornar dados na mesma função seja errado. Existem os prós e contras quando utilizamos um determinado padrão e com CQS não é diferente.
Um exemplo clássico e amplamente utilizado pela comunidade PHP que não faz uso do CQS é o Eloquent ORM. Como podemos ver abaixo, quando criamos um objeto a classe nos retorna um instância contendo os dados do objeto criado.

<?php
namespace App\Services;

use App\Domain\Users\UserTypes\BaseUser\Entities\UserEntity;

class UserService
{
    public function createEloquentUser(array $params = []): UserEntity
    {
        return UserEntity::create($params);
    }
}

Enter fullscreen mode Exit fullscreen mode

O retorno dessa função é uma instância de UserEntity:

Retorno da função createEloquentUser

Quando pensamos em principio da responsabilidade única, nossa função createEloquentUser acaba tendo duas responsabilidades: Criar o usuário e Retornar os dados do usuário em projetos pequenos isso acaba não sendo um problema, mas, quando nosso projeto começa a crescer o CQS pode nos oferecer as seguintes vantagens:

  • Performance: A maioria das aplicações executam mais leitura do que escrita então quando separamos as Queries e os Commands temos um ganho de perfomance, pois só executaremos essas ações onde é realmente necessário.

  • Mantenabilidade: Como cada classe e método terão apenas uma a responsabilidade de ler com as Queries ou escrever com os Commands torna o código mais legível pois temos mais clareza do que está sendo executado apenas ao olhar o código.

  • Fácil de testar: Como as responsabilidades das classes e métodos estão bem definidos acaba facilitando o processo de criação de testes unitários.

  • Fácil de implementar: É fácil de implementar, pois basta lembrar que se é uma consulta criaremos uma classe Query se é ação que cria ou altera alguma parte do nosso sistema criaremos uma classe de Command. O exemplo abaixo mostra as classes necessárias para um CRUD de usuários:

Implementação

A criação, atualização e deleção de usuários são considerados ações, pois, elas causam efeitos colaterais ao alterar o estado de onde estão armazenamos os dados dos nossos usuários. Então, para esses casos devemos criar classes de Command:

  • UserCreateCommand.php
<?php
namespace App\Infrastructure\User\Commands;

use App\Infrastructure\Database\DatabaseORM;

class UserCreateCommand
{
    public function __invoke(array $creationParams = []): void
    {
        DatabaseORM::create($creationParams);
    }
}
Enter fullscreen mode Exit fullscreen mode
  • UserUpdateCommand.php
<?php
namespace App\Infrastructure\User\Commands;

use App\Infrastructure\Database\DatabaseORM;

class UserUpdateCommand
{
    public function __invoke(int $userId, array $updateParams = []): void
    {
        DatabaseORM::update($userId, $creationParams);
    }
}
Enter fullscreen mode Exit fullscreen mode
  • UserDeleteCommand.php
<?php
namespace App\Infrastructure\User\Commands;

use App\Infrastructure\Database\DatabaseORM;

class UserDeleteCommand
{
    public function __invoke(int $userId): void
    {
        DatabaseORM::delete($creationParams);
    }
}
Enter fullscreen mode Exit fullscreen mode

Como nossas classes tem responsabilidades únicas e bem definidas então nesse caso optei por utilizar o método mágico __invoke. Note também que nossas funções sempre retornam void pois utilizando CQS o Command deve apenas executar uma ação e nada além disso.

Agora, para retornar os dados que foram armazenados vamos criar nossas classes de Query:

  • UserFindOneByIdQuery.php
<?php
namespace App\Infrastructure\User\Queries;

use App\Infrastructure\Database\DatabaseORM;

class UserFindOneByIdQuery
{
    public function __invoke(int $userId): array
    {
        return DatabaseORM::findOne($userId);
    }
}
Enter fullscreen mode Exit fullscreen mode
  • UserFindByQuery.php
<?php
namespace App\Infrastructure\User\Queries;

use App\Infrastructure\Database\DatabaseORM;

class UserFindByQuery
{
    public function __invoke(array $queryParams = []): array
    {
        return DatabaseORM::findBy($queryParams);
    }
}
Enter fullscreen mode Exit fullscreen mode

Mas nem tudo são flores, como você pode ver pra executar uma tarefa simples de CRUD de usuários foi preço criar 5 classes distintas e em um cenário com mais ações para cada outra ação ou consulta precisaríamos de mais classes, ou seja, apesar de termos um código mais limpo e organizado, temos um aumento na complexidade que nem sempre faz sentido em projetos pequenos. Por isso é importante ter em mente se as vantagens como ganho de perfomance e mantenabilidade a longo prazo realmente farão diferença invés de aplicar somente por estética.

Conclusão

O Command Query Separation é um padrão de projeto que como qualquer outro tem seus prós e contras, ele nos ajuda a compreender melhor como separar as responsabilidades das nossas ações e consultas. E como os outros padrões deve ser utilizado com sabedoria em casos onde as vantagens compensam as suas desvantagens. Quando mais nos aprofundamos em padrões de projeto e arquitetura de software mais ferramentas teremos pra enfrentar os problemas do dia-a-dia e escolher a melhor ferramenta para cada problema.

Top comments (0)