Operações isentas de efeitos colaterais

Assim, oa reconhecer o papel dessas operações na construção de sistemas de software, os desenvolvedores são capacitados a adotar abordagens mais específicas, como a segregação de operações e a simplificação de comandos complexos. A busca pela simplicidade, aliada a práticas robustas de teste, emerge como um orientador, apontando para um caminho onde a compreensibilidade do código e a manutenção eficiente convergem. Em última análise, ao internalizar esses princípios, os desenvolvedores estão equipados não apenas com ferramentas técnicas, mas também com uma filosofia que transcende linhas de código, moldando a essência de como construímos e evoluímos sistemas de software.

No desenvolvimento de software, a compreensão clara dos diferentes tipos de operações pode ser importante para construção de sistemas robustos e de fácil manutenção. Assim, neste artigo Operações isentas de efeitos colaterais, destacam-se três categorias fundamentais: comandos, consultas e funções. Então, cada uma dessas operações desempenha um papel único, trazendo consigo características importantes para o software.

Aqui no blog temos um grande conjunto de artigos que falam de Domain Driven Design. Esse, em si, tem como pano de fundo o DDD, embora a temática possa ser explorada em outras abordagens. Esse tema obtido de minha interpretação de parte do livro Domain Driven Design: Atacando as complexidades no coração do software, de Eric Evans. Mas, se puder, veja outros artigos que talvez sejam suplementares a esse:

Tipos de operações

Há, na prática 3 diferentes tipos de operações no desenvolvimento de software. Eventualmete chamamos do mesmo modo todas elas, mas vamos aqui definir melhor para nortear nosso entendimento.

Comandos

Esse tipo de operação é aquele que tem como propósito alterar o estado de um sistema. Ele é quem pode gerar efeitos colaterais com mudanças significativas no domínio. Eles podem ou não ter retornos explícitos.

using System;

// Classe que representa um usuário
public class Usuario
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public string Email { get; set; }
}

// Classe que possui o comando para atualizar um usuário
public class AtualizarUsuarioCommand
{
    // Propriedades do comando
    public int IdUsuario { get; }
    public string NovoNome { get; }
    public string NovoEmail { get; }

    public AtualizarUsuarioCommand(int idUsuario, string novoNome, string novoEmail)
    {
        IdUsuario = idUsuario;
        NovoNome = novoNome;
        NovoEmail = novoEmail;
    }

    // Método para executar o comando
    public void Executar(Usuario usuario)
    {
        // Lógica do comando: Atualizar as propriedades do usuário
        usuario.Nome = NovoNome;
        usuario.Email = NovoEmail;

        Console.WriteLine($"Usuário {IdUsuario} atualizado com sucesso.");
        Console.WriteLine($"Novo nome: {usuario.Nome}, Novo e-mail: {usuario.Email}");
    }
}

Consultas

Esse tipo é o contrário do comando, apenas obtendo um determinado dado, muitas vezes do banco de dados . Além disso as consultas sempre geram um retorno, mesmo que seja indicando a inexistência de um determinado item.


public class Usuario
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public string Email { get; set; }
}

public class ConsultarUsuarioPorIdQuery
{
    public int IdUsuario { get; }

    public ConsultarUsuarioPorIdQuery(int idUsuario) => IdUsuario = idUsuario;

    public Usuario Executar(Usuario[] usuarios) =>
        usuarios.FirstOrDefault(u => u.Id == IdUsuario);
}

Funções

As funções são um meio termo onde, por um lado, executam computações que podem ou não ter relação direta com o domínio, mas por outro lado, não podem gerar impactos indiretos.

public class Utilitarios
{
    // Função para obter a hora atual
    public static string ObterHoraAtual()
    {
        return DateTime.Now.ToString("HH:mm:ss");
    }
}

Operações isentas de efeitos colaterais

O termo efeito colateral faz mais sentido em inglês do que em português, mas estou usando por ser a tradução oficial. Mas entenda que em português efeito colateral dá uma ideia de algo negativo e não plenajado, mas o desejo é que signifique uma consequência apenas.

Nesse sentido, vamos levar em considerações que algumas operações têm efeitos colaterais (comandos) e outras não (consultas e funções). Quando um desenvolvedor vê um comando é interessante que ele seja capaz de discernir facilmente que tipo é. Desse modo ele poderá saber que operações exigirão dele um pouco mais, para sua melhor compreensão.

O contexto tradicional das CRUDs pode ser elemento facilitador do entendimento com o Create, Update e Delete sendo comandos e o Read sendo consulta. Entretanto quando estamos falando de Domain Driven Design o uso de CRUDs é uma contradição teórica: elas não pode existir. Em algum momento escreverei um artigo específico sobre isso. Mas, para o momento, o que importa é entender que tanto em cenários baseados em CRUDs quanto em DDD podem segregar esses diferentes tipos de operação.

Explosão combinatória

Então, como comentamos, os comandos produzem efeitos colaterais e os demais não: Esses podem gerar cenários de explosão combinatória. Assim, ao chamar um comando ele pode chamar outros que podem chamar mais outros e mais outros.

Imagem do Domain Driven Design exemplificando uma explosão combinatória de comandos chamando sub-comandos sucessivas vezes.

A dificuldade em visualizar e prever todo o impacto dessas reações pode resultar em consequências inesperadas. Mesmo um desenvolvedor experiente pode se deparar com a complexidade do sistema ao realizar uma mudança, especialmente quando diferentes partes do código estão interconectadas de maneiras intrincadas.

Vale destacar, também, que a intenção de uma dada ação em um sistema desenvolvido fica consideravelmente mais obscura no cenário dessa explosão combinatória. Tanto faz se você está criando uma operação ou utilizando uma.

Onde está o problema?

Bom, agora, com todo esse conhecimento, chegamos a conclusão de que o problema é o comando. Em especial aqueles que chamam outros comandos. Já as funções e consultas não doem muito. Mas o que podemos fazer para aliviar isso?

  • Segregue as operações: Ao perceber que uma dada operação pode ser separada em comando, função e consulta, então faça.
  • Segregue os comandos: um mesmo comando pode estar complexo demais e pode ser segregado em outros menores.
  • Simplificar os retornos: Todos os retornos de consultas devem ser alterados retornando ValueObjects, ou seja, imutáveis.
  • Garanta que funções não tenham efeitos colaterais: operações que executam computações não devem ter impactos ou efeitos colaterais. Deve-se evitar sempre que possível.
  • Construa testes: Tenha testes de unidade funcionando. Isso garante que 1: o código desenvolvido foi feito não somente para você mas para o outro desenvolvedor que vai ler; 2: o código discriminará todos os impactos em cadeia que porventura ocorram, deixando óbvio para outro desenvolvedor.
  • Evite entidades: não é papel das entidades lidar com complexidade muito grandes. Sempre que possível, evite que cenários desse tipo estejam aqui. Ao invés, procure usar serviços de domínio.
  • Prefira funções: se puder, use mais funções do que as comandos e consultas.
  • Simplifique as operações: Se tiver que usar operações, deixe-as as mais simples o possível.

Conclusão de Operações isentas de efeitos colaterais

Num cenário onde o código fonte é a espinha dorsal do desenvolvimento de software, compreender a natureza dos comandos, consultas e funções é algo realmente relevante. O artigo Operações isentas de efeitos colaterais delineou as características distintivas dessas operações, desde os comandos que moldam ativamente o estado do sistema até as consultas que se mantêm como observadoras passivas. Abraçando os princípios do Domain-Driven Design (DDD), exploramos estratégias para mitigar os desafios associados a efeitos colaterais e explosões combinatórias, oferecendo um guia para o desenvolvimento de sistemas mais claros e resilientes.

Assim, oa reconhecer o papel dessas operações na construção de sistemas de software, os desenvolvedores são capacitados a adotar abordagens mais específicas, como a segregação de operações e a simplificação de comandos complexos. A busca pela simplicidade, aliada a práticas robustas de teste, emerge como um orientador, apontando para um caminho onde a compreensibilidade do código e a manutenção eficiente convergem. Em última análise, ao internalizar esses princípios, os desenvolvedores estão equipados não apenas com ferramentas técnicas, mas também com um pensamento estruturado que transcende linhas de código, moldando a essência de como construímos e evoluímos sistemas de software.


Thiago Anselme
Thiago Anselme - Gerente de TI - Arquiteto de Soluções

Ele atua/atuou como Dev Full Stack C# .NET / Angular / Kubernetes e afins. Ele possui certificações Microsoft MCTS (6x), MCPD em Web, ITIL v3 e CKAD (Kubernetes) . Thiago é apaixonado por tecnologia, entusiasta de TI desde a infância bem como amante de aprendizado contínuo.