C# - Usando records como Ids fortemente tipados - I


 Hoje veremos como usar o tipo record para definir um identificador único(Id) fortemente tipado usado nas entidades do modelo.

Quando criamos as entidades em um sistema geralmente definimos uma propriedade do tipo integer, GUID ou mesmo string para identificar a entidade pois esses tipos são suportados pelos banco de dados.

No entanto, se todas as suas entidades tiverem ids do mesmo tipo, será muito fácil misturá-los e usar o id de um produto onde o id de um pedido era esperado. Esta é, na verdade, uma fonte comum de erros.

Exemplo:

public void AdicionarProduto( int  pedidoId , int produtoId )
{
   ...
}

...

AdicionarProduto(produtoId, pedidoId);
 

No exemplo acima vemos que a chamada do método AdicionarProduto() informou o valor de produtoId e pedidoId invertendo a ordem esperada dos valores, e como eles são do tipo primitivo int o compilador não vai indicar o erro.

Naturalmente podemos resolver esse problema definindo um Identificador fortemente tipado declarando um tipo para o Identificador(Id) de cada entidade. No exemplo podemos definir os tipos PedidoId e ProdutoId.

public void AdicionarProduto( PedidoId  pedidoId , ProdutoId produtoId )
{
   ...
}

...

AdicionarProduto(produtoId, pedidoId);
 

No código acima, cometemos o mesmo erro, invertendo os valores produtoid e pedidoid, mas agora os tipos são diferentes, e, são fortemente tipados, então o compilador vai detectar e vai relatar um erro.

Definindo um Id fortemente tipado

A solução adotada acima consiste na criação dos tipos PedidoId e ProdutoId. Como exemplo podemos ter o código abaixo usado para definir o ProdutoId:

public readonly struct ProdutoId : IEquatable<ProdutoId>
{
        public ProdutoId(int value)
        {
            Value = value;
        }

        public int Value { get; }

        public bool Equals(ProdutoId other) => other.Value == Value;
        public override bool Equals(object obj) => obj is ProdutoId other && Equals(other);
        public override int GetHashCode() => Value.GetHashCode();
        public override string ToString() => $"ProdutoId {Value}";
        public static bool operator ==(ProdutoId a, ProdutoId b) => a.Equals(b);
        public static bool operator !=(ProdutoId a, ProdutoId b) => !a.Equals(b);
}

Essa é uma abordagem válida e que pode ser usada, mas temos que escrever o código acima para cada entidade em nosso modelo, o que não é muito agradável.

Não haveria uma forma mais simples de resolver isso ?

Sim, esta outra opção seria usar o novo recurso do C# 9 chamado record.

Usando o tipo record

Os tipos record são tipos de referência com imutabilidade integrada e semântica de valor.

Eles fornecem implementações para todos os membros que escrevemos manualmente no código anterior (Equals, GetHashCode, etc) e oferecem uma sintaxe muito concisa conhecida como registros posicionais.

Se reescrevermos nosso tipo ProdutoId usando um record, teremos o seguinte código :


  public
record ProdutoId(int Value) ;        

 

E, isso é tudo.

Apenas uma linha de código faz o que a nossa implementação manual fez com dez linhas.

A principal diferença é esta: Nossa implementação manual foi feita usando uma struct , ou seja, um tipo de valor, mas os records são tipos de referência, o que significa que podem ser nulos.  (Pode não ser um grande problema, especialmente se você usar tipos de referência anuláveis, mas é algo para se ter em mente.)

Dessa forma, definir um id fortemente tipado para cada entidade em nosso modelo não é mais uma tarefa assustadora; obtemos os benefícios de um tipo forte tipado quase de graça.

Naturalmente, existem outras questões que teremos que considerar, como serialização JSON, uso com Entity Framework Core, etc., mas vamos continuar a tratar do assunto na outra parte do artigo.

"Sei estar abatido, e sei também ter abundância; em toda a maneira, e em todas as coisas estou instruído, tanto a ter fartura, como a ter fome; tanto a ter abundância, como a padecer necessidade.
Posso todas as coisas em Cristo que me fortalece."
Filipenses 4:12,13

Referências:


José Carlos Macoratti