Entidade Base

Entidade Base

Olá tudo tudo bem? Seguindo a série de artigos sobre DDD no primeiro post dessa série sobre DDD falei sobre os conceitos de Entidade vs Objeto de Valor. Se você costuma seguir os princípios do DDD, já deve ter ouvido falar sobre a importância de criação de uma classe base, chamada Entidade, em sua aplicação. Também imagino que tenha se questionado do porque de existir tanta implementação que, muitas vezes, não fazia sentido para você, permitir que continuassem implementadas, então normalmente uma classe base tem apenas uma propriedade Id, certo?. Vou "sugerir" que é uma excelente idéia PERMANECER com esses códigos implementados na classe base chamada: Entidade, permitindo ter um único lugar de implementação das lógicas básicas para validação de uma entidade.

Existem alguns questionamentos que são inevitáveis, quando você toma a decisão de implementar esse conceito em seu projeto:

  • O que eu deveria implementar em uma classe base: Entidade?
  • Como tudo isso deveria ser apresentado em uma classe base: Entidade.

Bom, existe uma série de tópicos que podemos extrair dessas duas perguntas, e é isso que faremos agora!!!

Implementando Interface para Entidade Base

Quando se pensa em reuso e flexibilidade, logo pensamos em implementar uma Interface Base, para resolver esse tipo de problema. E porque não?! Não é mesmo!?

Olhando o bloco de código abaixo, podemos ver, nitidamente, que o uso de uma Interface pode auxiliar no suporte a reuso:

public interface IEntidade  
{
    public Guid Id { get; }
}

Embora essa abordagem garanta que todas as classes que, implementarem a interface IEntidade, tenham funcionalidades em comum, o que neste caso será a propriedade Id; na maioria dos casos, essa abordagem é péssima e fere alguns princípios e conceitos.

O primeiro ponto importante que gostaria de frisar é que: Interfaces não permitem que você compartilhe implementações que possam ser reaproveitadas por outras classes, apenas permite que todas as classes que implementam a interface IEntidade, terão como apenas a propriedade Id. O que isso traz de más práticas? Cada classe terá sua própria implementação sobre a propriedade Id , o que não garantirá que todas as implementações sejam iguais. Esse tipo de abordagem de implementação, fere o princípio DRY (Don't Repeat Yourself).

O segundo ponto importante é que: Interfaces garante um contrato de implementação. Ou seja, quando duas classes precisam implementar uma interface, apenas garantirá que ambas as classes terão as mesmas assinaturas dos métodos que existem na interface, mas isso não garante a mesma LÓGICA e, além disso, também não garante que ambas as classes, embora tenham a mesma propriedade Id, sejam iguais. Em outras palavras, quando decidimos utilizar uma interface, estamos trabalhando o conceito "can-do", segundo Bertrand Meyer’s. Por outro lado, quando estamos trabalhando com herança de classe base, no caso Entidade, estamos trabalhando o conceito "is-a", o que garante o relacionamento entre entidades.

Melhorando um pouco mais a Implementação da classe "Entidade"

OK! Agora que entendemos que trabalhar com interface, na maioria dos casos, não é algo benéfico para a implementação de uma classe base, como por exemplo: Entidade. O que deveriamos implementar de lógica na classe base, para que as classes de dominio possam herdar de funcionalidades em comum e sejam integras?

Obviamente, toda entidade de dominio possui um Id e, também, toda tabela num banco de dados, seja ele Relacional ou NoSQL tem um Id. Então, vamos implementar classes genéricas para facilitar e flexibilizar nossos códigos, pois quando for necessário implementar uma entidade de dominio que possua um Id do tipo Guid, é possível ter uma entidade base funcionando com a propriedade Id do tipo Guid ou quando for necessário uma propriedade Id do tipo long, int, string, também é possível definir e flexibilizar a classe Entidade Base. Isso parece ser uma iniciativa bem interessante, não é?

Abaixo segue a implementação de uma classe base Entidade:

public class Entidade<TTipoIdentificador>  
{
    public ITipoIdentificador Id { get; }
}

Qual o problema que temos nessa implementação? Aparente e tecnicamente, não há nenhum problema. Mas, temos sim um pequeno overimplementation (super-implementação). Qual? PrematureGeneralization. Com base no conceito Premature Generalization, é possível entender que não há necessidade usar uma classe tão genérica e tão flexível, a ponto de reusar em vários projetos ou contextos delimitados, até porque cada projeto/contexto delimitado, possui sua própria estrutura de modelagem, única, de dados.

Implementando a classe "Entidade" seguindo boas práticas

Bom, já que falamos de conceitos de implementação de interface que não traz nenhum valor agregado para este cenário particular de uma classe base: Entidade, e também não seria interessante termos uma classe base genérica. Como deveria ser implementada a classe base: Entidade?

PS.: Esse código é utilizado em produção

public abstract class Entidade  
{
    public int Id { get; protected set; }

    public override bool Equals(object entidade)
    {
        var entidadeComparacao = entidade as Entidade;

        if (ReferenceEquals(entidadeComparacao, null))
            return false;

        if (ReferenceEquals(this, entidadeComparacao))
            return true;

        if (GetType() != entidadeComparacao.GetType())
            return false;

        if (!Transitorio() && !entidadeComparacao.Transitorio() && Id == entidadeComparacao.Id)
            return true;

        if (Transitorio() || entidadeComparacao.Transitorio())
            return false;

        return Id == entidadeComparacao.Id;
    }

    public virtual bool Transitorio()
    {
        return Id == 0;
    }

    public static bool operator ==(Entidade entidadeA, Entidade entidadeB)
    {
        if (ReferenceEquals(entidadeA, null) && ReferenceEquals(entidadeB, null))
            return true;

        if (ReferenceEquals(entidadeA, null) || ReferenceEquals(entidadeB, null))
            return false;

        return entidadeA.Equals(entidadeB);
    }

    public static bool operator !=(Entidade entidadeA, Entidade entidadeB)
    {
        return !(entidadeA == entidadeB);
    }

    public override int GetHashCode()
    {
        return (GetType().ToString() + Id).GetHashCode();
    }
}

A primeira característica que gostaria que você coloca-se sua atenção, seria a propriedade Id. Perceba que nesta implementação, a propriedade Id foi configurada para um tipo específico: int. Com isso eu garanto que todos os Ids dentro das classes de dominio possuirão, também, uma propriedade Id do tipo int. Isso exclui, nessa implementação, o overimplementation (superimplementação), que ao meu ver é desnecessária para esse tipo de implementação.

PS.: Eu entendo que, a implementação de uma classe genérica, trabalhando sobre o overimplementation, só faria sentido no desenvolvimento de um framework, onde você não conhece o repostório externo. Dessa forma, a implementação genérica facilitaria a utilização dessa classe Entidade genérica.

A implementação acima, traz uma parte interessante e que merece ser explicada: Tipos de Igualdade ou Fator de Igualdade:

  • Por Identificador (Identifier Equality) - Significa que dois objetos são iguais, quando ambas possuem suas propriedades Id, iguais.

  • Por Referência (Reference Equality) - Significa que dois objetos são iguais, em memória, quando são comparados.

  • Por Estrutura (Structural Equality) - Significa que dois objetos são iguais, quando ambos possuem suas propriedades com os mesmos valores.

Isso significa que uma Entidade está inserida nos dois primeiros Tipos de Igualdade. O último tipo de igualdade, pertence ao Objeto de Valor (cobrirei ele em outro post). Vamos seguir analisando outras características que estão presentes na última versão do código da entidade base: Entidade.

Se for possível perceber, o primeiro método tem a implementação da lógica de validação se uma Entidade é igual a uma outra entidade, a nível de referência em memória, mas também tem uma outra característica interessante que é a implementação do método Transitorio(), o qual implementa uma comparação da propriedade Id == 0. Isso quer dizer que se a entidade Id for igual a '0', essa entidade ainda não foi persistida na base de dados, caso a propriedade 'Id' seja diferente de '0', a entidade foi persistida na base de dados.

A penúltima característica que gostaria de apresentar, sobre a implementação da classe base: Entidade, é a sobreescrita (override) do método GetHashCode(). Essa implementação em conjunto com a implementação do Equals(), torna possível que o mecanismo de comparação interna do .NET.

PS.: Para quem tem o Resharper, recomendo analisar o código fonte .NET para entender melhor como funciona.

Observe o código abaixo:

public class Cliente : Entidade  
{}

Cliente clienteA = new Cliente();  
Cliente clienteB = new Cliente();

if (clienteA == clienteB)  
{}

ou

public class Cliente : Entidade  
{}

List<Cliente> clientes = new List<Cliente>();  
Cliente clienteA = new Cliente();  
clientes.Add(clienteA);

if (clientes.Contains(clienteA))  
{}

Ambos os códigos acima, são completamente possíveis e estão 100% aderentes aos mecanismos de lógica comparativa do .NET Internals, além da sobreescrita dos métodos Equals() e GetHashCode(), é muito recomendado que você, também, implemente a sobrecarga de operadores != e ==, isso vai favorecer as estruturas de comparação internas do .NET.

Conclusão

Todo o código apresentado aqui, é a base de implementação que uso em meus projetos e estão em produção. A intenção desse post, foi apresentar algumas preocupações sobre a implementação de uma classe base para Entidade e as boas práticas de implementação e as vantagens que temos ao implementa-las, junto com os mecanismos internos do .NET.

Um conselho que eu deixo para todos é: EVITEM OBJETOS DEUSES (God Objects) e implementem apenas o que for necessário e fizer, realmente sentido ser implementado (YAGNI).

Recentemente fiz um Cast com o time da Software em Contexto falando sobre: Modelagem tática: Dando voz ao seu domínio, curte o canal para você ser avisado dos próximos casts.

Comments