Design

19 nov, 2021

Value Object na abordagem do Domain Driven Design

Publicidade

Hoje vamos apresentar o conceito de Value Object, termo cunhado por Eric Evans em seu livro Domain-Driven Design: Tackling Complexity in the Heart of Software. Um Value Object(VO) na abordagem do Domain Driven Design é um objeto que representa um valor e não possui identidade, servindo para dar característica a outro objeto. Dessa forma a identidade de um VO está ligada à composição de seus valores e não a uma propriedade de identidade.

Com base neste conceito dois Value Objects são iguais quando possuem o mesmo valor não sendo necessariamente o mesmo objeto.

Como exemplo de Value Objects podemos citar objetos que representam Nome, Endereco, Email, Dinheiro, etc.

Um objeto Dinheiro com valor R$ 5,00 é igual a outro objeto de mesmo valor; um objeto Nome com valor ‘Jose‘ é igual a outro objeto de mesmo valor não importando a instância do objeto.

Se um objeto dentro do seu domínio não guarda estado e não precisa existir se não estiver relacionado com nenhuma outra Entidade, então considere-o como forte concorrente a ser um VO.

Os Value Objects devem ser imutáveis e pequenos e representam algo único como quantidades, descrições simples, valores, etc.

As diretrizes que podemos usar para projetar um Value Object são:

– Ser imutável
– Seu único uso é como propriedade de uma ou mais entidades ou mesmo outro VO;
– Saber usar todas as suas propriedades ao realizar qualquer tipo de verificação de igualdade, incluindo aquelas que baseiam sua comparação em códigos hash;
– Sua lógica não deve ter efeitos colaterais no estado fora do VO;

Um exemplo de um Value Object :  NomeCompleto

A seguir veremos um tipo que pode ser reutilizado em todo o sistema e que representa o nome de uma pessoa que vamos chamar de NomeCompleto. Podemos usar este tipo para Clientes, Usuários, Fornecedores, etc.

Vamos partir da definição da seguinte regra de negócio :

1- Toda a pessoa deve ter um nome e um sobrenome

Com base nisso definimos a classe NomeCompleto com o código a seguir:

public class NomeCompleto
{
private string _nome;
private string _sobrenome;
        public static NomeCompleto Create(string nome, string sobrenome)
{
return new NomeCompleto(nome, sobrenome);
}
private NomeCompleto(string nome, string sobrenome)
{
_nome = nome;
_sobrenome = sobrenome;
}

 public string Nome => _nome;
public string Sobrenome => _sobrenome;

}

Analisando o código temos que :

1 – Temos dois campos privados _nome e _sobrenome

2 – O método factory Create obriga que na criação de um NomeCompleto temos que passar as duas partes do nome. Assim nunca teremos um estado inválido;

3 –  O factory passa esses valores para o construtor que define os valores do campo;

4 –  A seguir atribuímos os valores às propriedades Nome e Sobrenome usando o recurso expression body. As propriedades somente sabem retornar os valores dos campos.

Vamos destacar o seguinte nesta classe :

  • Não temos uma propriedade de identidade aqui. O nome nunca será rastreado sozinho. Essa classe só será usada como uma propriedade de algum outro tipo;
  • tipo já é imutável – depois de criar o objeto, não há como modificá-lo. Se você informou o nome ou sobrenome errado, basta criar uma nova instância. Exatamente como você faria com uma string.

A seguir temos o código de uma classe Cliente que usa o Value Object NomeCompleto:

    public class Cliente
{
private Guid _id;
private NomeCompleto _nome;
        public Cliente(string nome, string sobrenome)
{
_id = Guid.NewGuid();
_nome = NomeCompleto.Create(nome, sobrenome);
        }public Guid Id => _id;
public NomeCompleto Nome => _nome;

        public void CorrigirNomeCliente(string nome, string sobrenome)
{
_nome = NomeCompleto.Create(nome, sobrenome);
}
}

Na classe Cliente temos que :

– O construtor exige que seja fornecido os valores de nome e sobrenome e a seguir os usa para criar um objeto NomeCompleto junto com um Guid gerado para sua identidade;

Nota:  Guid ou Globally Unique Identifier – É um identificador único universal é um número de 128 bits usado para identificar informações.

– Se precisar alterar o nome, você pode chamar o método CorrigirNomeCliente para recriar esse nome de imediato.

O DDD nos orienta a pensar sobre os comportamentos em nossas entidades, e o método CorrigirNomeCliente representa um comportamento necessário para resolver um problema específico.

– Só porque NomeCompleto é imutável, não significa que o tipo não pode ser inteligente. Se precisarmos de outras maneiras de visualizar nomes podemos incorporar os comportamentos no tipo NomeCompleto.

Por exemplo, se eu quiser apresentar o nome completo em ordem alfabética pelo sobrenome posso implementar estes comportamentos na entidade Cliente.

Assim NomeCompleto agora é um tipo imutável que possui dois valores e nenhum identificador.

Para que este NomeCompleto seja um Value Object  legítimo ainda preciso garantir que posso comparar dois tipos de NomeCompleto para determinar a igualdade.

E preciso de flexibilidade para fazer isso usando o método Equals herdado de todos os objetos na linguagem C#, bem usar os operadores == ou! =.

Comparar duas instâncias deste objeto de valor com Equals significa comparar cada um dos valores nos objetos.

A seguir temos a implementação dos métodos de comparação :

using System;
using System.Collections.Generic;
namespace CShp_VO1
{
public class NomeCompleto : IEquatable<NomeCompleto>
{
private string _nome;
private string _sobrenome;
        public override bool Equals(object obj)
{
return Equals(obj as NomeCompleto);
}
        public bool Equals(NomeCompleto outronome)
{
return outronome != null &&
Nome == outronome.Nome &&
Sobrenome == outronome.Sobrenome;
}
        public override int GetHashCode()
{
return HashCode.Combine(Nome, Sobrenome);
}
        public static bool operator ==(NomeCompleto left, NomeCompleto right)
{
return EqualityComparer<NomeCompleto>.Default.Equals(left, right);
}
        public static bool operator !=(NomeCompleto left, NomeCompleto right)
{
return !(left == right);
}
        public static NomeCompleto Create(string nome, string sobrenome)
{
return new NomeCompleto(nome, sobrenome);
}
private NomeCompleto(string nome, string sobrenome)
{
_nome = nome;
_sobrenome = sobrenome;
}

 public string Nome => _nome;
public string Sobrenome => _sobrenome;

}
}

Assim, acabamos de definir um Value Object NomeCompleto que esta sendo usado pela Entidade Cliente.