DEV Community

Cover image for Dê significado aos seus dados: Objetos de Valor na prática
Meriéli Manzano
Meriéli Manzano

Posted on

Dê significado aos seus dados: Objetos de Valor na prática

Pare de usar string para tudo e comece a expressar suas regras de negócio com clareza e segurança

Você já se pegou repetindo as mesmas validações de CPF, e-mail ou telefone em várias partes do sistema? Sentiu que seus modelos de domínio estão anêmicos, cheios de getters e setters, mas sem comportamento real? Se sim, é hora de conhecer o verdadeiro poder dos Objetos de Valor (Value Objects).

Neste artigo, vamos mergulhar fundo nesse conceito transformador. Você vai entender por que Objetos de Valor garantem consistência, eliminam duplicação e tornam seu código mais expressivo e seguro. Prepare-se para elevar o nível da sua modelagem de domínio!

Imagine um CPF: 123.456.789-00. O que importa é o número em si, não uma identidade única que o acompanhe. Se dois CPFs têm o mesmo número, eles são iguais – não importa se foram instanciados em momentos diferentes ou em lugares distintos. Essa é a alma do Objeto de Valor.

Definição: Um Objeto de Valor é um tipo de classe que representa um conceito abstrato, sem identidade própria (não possui ID). Ele é definido apenas por suas propriedades, encapsulando dentro de si um ou mais valores e suas respectivas regras de negócio.

Características fundamentais:

  • Imutável: uma vez criado, nunca muda. Para alterar, cria-se uma nova instância.
  • Comparado por atributos: dois Value Objects são iguais se todos os seus atributos forem iguais.
  • Auto-validado: ao ser instanciado, já garante que o valor é consistente com as regras do domínio.

Exemplos clássicos:

  • CPF, CNPJ, CEP, telefone
  • Endereço (composto por logradouro, número, cidade, CEP)
  • Email, senha hash, duração de tempo, preço, nome próprio, cor, moeda

Por que priorizar Objetos de Valor?

Muitos desenvolvedores, quando pensam em DDD, correm imediatamente para as Entidades. Mas o próprio Eric Evans traz uma orientação poderosa:

Devemos nos esforçar para modelar utilizando Objetos de Valor, em vez de Entidades, sempre que possível. Mesmo quando um conceito de domínio precisa ser modelado como uma Entidade, o projeto da Entidade deve favorecer o uso de um contêiner de valores, em vez de um contêiner de Entidades filho.

Isso significa que o primeiro local para armazenar nossas regras de negócio é no Objeto de Valor, e por isso uma Entidade precisa compor Objetos de Valor, evitando ter Entidades dentro de Entidades.

Os benefícios do Objeto de Valor são enormes:

  • Reuso imediato: Um Email ou CPF pode ser usado em qualquer lugar (Usuário, Cliente, Fornecedor) sem repetir regras.
  • Código mais expressivo: Em vez de string $email, temos Email $email. O tipo já revela a intenção e as restrições.
  • Testabilidade facilitada: Testar as regras de um Value Object isolado é trivial.
  • Imutabilidade e segurança: Sem side-effects; objetos podem ser compartilhados sem risco.

Mesmo que seu projeto não adote formalmente o DDD (Domain-Driven Design, uma abordagem que coloca o negócio e seu vocabulário no centro do código), usar objetos de valor vale a pena porque eles eliminam a duplicação de validações e garantem consistência em qualquer arquitetura.

Se até aqui você já aprendeu algo, compartilhe este artigo com quem precisa de mais consistência e menos duplicação.

Sinal de alerta para extrair um Value Object

Quando você perceber várias classes com os mesmos atributos (ex.: string $cpf, string $email, string $telefone), cada uma validando do seu próprio jeito, é um forte indicativo de que esses atributos merecem se tornar Objetos de Valor.

Objetos de Valor vs Entidades: quando usar cada um?

Entidades

  • Identidade: Possui um ID único que a rastreia ao longo do tempo e o que a distingue de outras entidades.
  • Mutabilidade: Mutável (seus atributos podem mudar, mas a identidade permanece).
  • Igualdade: Comparação por identidade (ID)
  • Exemplos de uso: Pessoa, Pedido, Conta Bancária, Produto

Objetos de Valor

  • Identidade: Não tem identificador único; é definido por seus atributos ou por seu valor.
  • Mutabilidade: Imutável.
  • Igualdade: Comparação estrutural (todos os atributos/valores).
  • Exemplos de uso: CPF, Email, Endereço, Cor, Dinheiro

Construindo um Value Object na Prática

Vamos implementar um Email, usando uma classe abstrata genérica para reaproveitar o método equals e definir por padrão o $value como readonly.

// Shared/ValueObject.php
namespace Shared;

/**
 * @template TInput
 */
abstract class ValueObject
{
    /**
     * @param  TInput  $value
     */
    public function __construct(public readonly mixed $value) {}

    /**
     * @param  self<TInput>  $other
     */
    public function equals(self $other): bool
    {
        return static::class === $other::class && $this->value === $other->value;
    }

    /**
     * @param  self<TInput>  $other
     */
    public function notEquals(self $other): bool
    {
        return !$this->equals($other);
    }
}
Enter fullscreen mode Exit fullscreen mode

Agora, o Value Object concreto Email:

namespace Domain\Account\User\Domain\ValueObjects;

use Domain\Account\User\Domain\Exceptions\InvalidEmailException;
use Shared\ValueObject;

/**
 * Email value object handles domain validation for emails.
 *
 * @extends ValueObject<string>
 */
final class Email extends ValueObject
{
    public function __construct(string $value)
    {
        $trimmed = mb_strtolower(mb_trim($value));

        if (!filter_var($trimmed, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidEmailException("The email '{$value}' is invalid.");
        }

        parent::__construct($trimmed);
    }

    public string $local {
        get => explode('@', $this->value)[0];
    }

    public string $domain {
        get => explode('@', $this->value)[1];
    }
}
Enter fullscreen mode Exit fullscreen mode

Observações importantes:

  • O construtor já valida e normaliza (minúsculas, trim).
  • As propriedades $local e $domain são derivadas do valor.
  • O objeto é readonly – imutável por design.
  • A comparação equals verifica tanto o tipo quanto o valor.

Cuidado: a lógica de negócio dentro do Value Object deve ser autossuficiente e baseada apenas no valor recebido, e não deve depender de contexto externo (como banco de dados, por exemplo).


Objetos de Valor em uso

Uma Entidade no DDD deve ser um contêiner de valores, não uma colcha de retalhos de tipos primitivos. Veja como fica uma UserEntity:

class UserEntity extends Entity  // Entity pai fornece $id e clone()
{
    private function __construct(
        public readonly Name $name,
        public readonly Email $email,
        public readonly ?HashedPassword $password,
        string $id,
    ) {
        parent::__construct($id);
    }
    // Restante do código: factory methods, comportamentos...
}
Enter fullscreen mode Exit fullscreen mode

Note que $name, $email e $password são Objetos de Valor e cada um carrega suas próprias regras. E a Entidade User não precisa se preocupar com essas validações – elas já foram resolvidas no momento da criação.


Dicas para usar Objetos de Valor

  1. Comece pelos primitivos obcecados: qualquer lugar onde você vê string $cpf, string $cep, string $telefone é candidato a Value Object.
  2. Seja imutável: depois de construído, nada pode mudar. Para alterar, crie um novo objeto.
  3. Valide no construtor: lance exceções específicas do domínio se os dados forem inválidos. Assim, um objeto de valor sempre representa um estado consistente.
  4. Implemente equals corretamente: compare o valor na classe e todos os atributos relevantes.

Ao adotar Value Objects, você:

  • Elimina duplicação de validações.
  • Torna o código autodocumentado.
  • Previne estados inválidos.
  • Facilita a evolução do domínio.

Desafio: Liste 3 atributos do seu sistema atual que são primitivos mas deveriam ser Value Objects e deixe aqui nos comentários. Exemplo: placaVeiculo, numCartaoCredito, intervaloData.


Objetos de Valor não são exclusivos para quem adota o DDD – são uma ferramenta prática para acabar com a bagunça dos tipos primitivos e validações repetidas. Eles transformam regras de negócio escondidas em código limpo, seguro e reutilizável.

Então lembre-se: todo CPF, e-mail ou telefone no seu sistema é um valor que merece respeito. Trate-os como objetos – e eles tratarão bem o seu domínio.

Agora vá, identifique um atributo extraia seu primeiro Value Object, e sinta a diferença na consistência. Seu eu do futuro vai agradecer.

Gostou? Então não perca os próximos. Me siga e receba mais artigos sobre código de qualidade e arquitetura de software de verdade.

🔔 Compartilhe com quem ainda valida CPF com if solto no código.

Vamos juntos construir softwares mais expressivos e robustos! Até a próxima, galerinha.

Top comments (0)