Sistemas sempre têm dependências, seja um método falando com outro, uma classe, um módulo ou estruturas distribuidas. Então, a questão é que quanto mais dependências mais complexo é sua evolução. Por isso Eric Evans disse que para se ter um design flexível algumas práticas devem ser adotadas, se o sistema for orientado a domínios. Entender os contornos conceituais e classes autônomas são bons começos, mas ainda assim há oportunidades de melhorias nesse design. O que o autor chama de Fechamento de Operações dará outra perspectiva sobre algo bastante natural no dia-a-dia.
Sumário
1+1 = 2
Na matemática sabemos que se aplicarmos a dois números reais uma soma, o resultado será outro número real. Portanto, é possível aplicar novas somas a ele se os parâmetros seguirem o mesmo tipo. Na computação não é diferente, a soma de inteiros resulta em um inteiro. Deixo isso claro para entendermos que esse é um comportamento absolutamente natural e cotidiano; e que não se dá grande importância.
Acontece que ao fazer operações desse tipo temos a felicidade de notar que há um conceito claro para qualquer desenvolvedor ou mesmo usuário final: somas de inteiros sempre resultam inteiros. Se uma operação ocorrer com outros tipos é possível que tal conclusão não seja tão clara e uma carga cognitiva pode ser adicionada.
Entendendo isso, vamos expandir o conceito. Um método Xpto pode receber um objeto de uma classe por parâmetro, retornando o mesmo tipo. Quando isso ocorre dizemos que: operação de Xpto fechada sob T ou no caso dos inteiros; já a operação de adição é fechada sob o conjunto dos números inteiros. Veja o exemplo em C# a seguir.
public T Xpto<T>(T t_objeto);
public int Soma(int i, int j);
Trabalhando com abstrações
O mesmo conceito se aplica quando falamos de classes abstratas ou mesmo interfaces. Estou dizendo com isso que uma classe concreta pode retornar sua superclasse abstrata, mantendo o mesmo conceito verdadeiro. Veja um exemplo hipotético em C# ou Java que tem AnalistaDeSistemas herdando de Funcionario que implementa IFuncionario.
public Funcionario ObterSuperiorImediato(AnalistaDeSistemas a);
public IFuncionario ObterSuperior(AnalistaDeSistemas a);
Não invente conceitos, feche as operações
Um aspecto complementar ao dito acima é que deve-se presar os tipos corretos para o retornos dos métodos. Estou dizendo com isso que se um método retorna um inteiro e esse é o jeito mais claro de entender seu significado: então retorne inteiro! Não é necessário criar um tipo específico para tal. Veja um exemplo a seguir.
// Não é necessário usar um tipo Size para indicar o retorno
public Size GetSizeInSquareMeters(Place place);
// Ao invés disso é possível retornar um inteiro
public int GetSizeInSquareMeters(Place place);
Ao fazer um retorno para um tipo primitivo o significado é igual, sua aplicação é igual mas não há nenhuma dúvida para quem ler. Ao contrário, quando há o tipo Size o desenvolvedor terá que entender o que ele é e como ele se comporta. Veja que a depender do domínio é necessário criar a classe Size, mas isso nem sempre é verdadeiro: pondere!
Fechamento de Operações, ValueObjects e Entities
Os objetos de construção do Domain Driven Design foram criados para suportar os conceitos do design flexível, como o Fechamento de Operações. Note que a natureza de um Value Object é sustentar um fechamento por sua imutabilidade. Com isso é comum e recomendado que operações com essa natureza façam parte de VOs. Vamos voltar ao exemplo do funcionário.
public class Funcionario
{
public string Nome { get; }
public string Cargo { get; }
public Funcionario[] Subordinados { get; }
public Funcionario(string nome, string cargo)
{
Nome = nome;
Cargo = cargo;
}
// Método para obter o superior imediato
public Funcionario ObterSuperiorImediato()
{
// Lógica para obter o superior imediato, pode ser personalizada conforme necessidade
// Aqui, estamos assumindo uma lógica simples
Console.WriteLine($"Obtendo o superior imediato de {Nome} ({Cargo})");
// Poderia ser uma lógica mais complexa baseada em regras de negócio
// Exemplo: consultando um banco de dados, verificando hierarquia, etc.
// Retornando um Funcionario fictício como exemplo
return new Funcionario("Superior", "Gerente");
}
}
Por outro lado temos as Entities (Entidades) que são um objeto bem estabelecido do domínio e, consequentemente, do processo. Ou seja, ele vai sofrendo as possíveis formas que fazem sentido para o domínio por não ser imutável. Note que o fechamento de operações para Entities não é algo esperado, mas pode ser que seja possível usar em situações específicas, a depender da criatividade de quem o programa.
Conclusão do Fechamento de Operações
Controlar a complexidade do software é quase um mantra para quem adota Domain Driven Design. É muito fácil aumentar a complexidade mas isso gerará dificuldades para novos desenvolvedores, afastará o código da visão do usuário final e consequentemente aumentará o custo. Por outro lado há diversas formas de reduzir essa complexidade. Então, o artigo ‘Fechamento de Operações’ mostra essa técnica onde considera que tipos e retornos iguais facilitam a compreensão por ser uma espécie de contrato implícito. Assim, o post desenvolve essa temática mostrando exemplos práticos de como os blocos de construção do DDD (Value Objects e Entities) se relacionam com essa técnica.
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.