No desenvolvimento de software, especialmente ao aplicar boas práticas de arquitetura e o Design Orientado a Domínio (DDD), é muito comum se deparar com os termos DTO (Data Transfer Object) e VO (Value Object).

Embora ambos pareçam estruturas simples de dados à primeira vista, eles possuem propósitos, regras e comportamentos completamente diferentes. Misturá-los pode inflar sua lógica de negócios ou comprometer a integridade dos seus dados.

Neste artigo, vamos desmistificar esses dois conceitos, entender a finalidade de cada um, descobrir o cenário ideal para utilizá-los e ver um exemplo prático em PHP.

O que é um DTO (Data Transfer Object)? Link para o cabeçalho

Como o próprio nome diz, o Data Transfer Object é um objeto projetado exclusivamente para o transporte de dados entre diferentes partes de um sistema ou entre sistemas distintos (como uma API e um cliente).

Finalidade do DTO Link para o cabeçalho

A principal meta de um DTO é agrupar uma quantidade de dados que precisa ser enviada de um ponto A para um ponto B, reduzindo o número de chamadas de métodos ou requisições remotas.

Regra de Ouro do DTO: Ele é “burro”. Um DTO não possui comportamento, não possui lógica de negócios e, geralmente, consiste apenas em propriedades públicas ou métodos getters e setters. Ele serve apenas como uma “sacola de dados”.

Cenários ideais para DTOs Link para o cabeçalho

  • Entrada de requisições (Payloads): Capturar os dados que vêm de um formulário ou de um JSON enviado para a sua API.
  • Saída de respostas (Responses): Filtrar e estruturar os dados que saem do seu banco de dados antes de enviá-los para o front-end, garantindo que senhas ou dados sensíveis não sejam expostos.
  • Comunicação entre microsserviços: Enviar dados através da rede de forma estruturada.

O que é um VO (Value Object)? Link para o cabeçalho

O Value Object (Objeto de Valor) é um conceito fundamental do DDD. Ele representa um conceito do seu domínio de negócios que é definido inteiramente pelos seus atributos, e não por uma identidade única (como um ID de banco de dados).

Por exemplo, uma nota de R$ 50,00 é um Value Object. Você não se importa com o número de série específico dela (identidade) ao comprar um café, você se importa apenas com o fato de que ela vale 50 reais (o valor). Se você trocar aquela nota por outra de 50 reais, o resultado é rigorosamente o mesmo.

Finalidade do VO Link para o cabeçalho

Dar significado e trazer segurança para o domínio do sistema. Ele encapsula regras de validação de negócios e garante que os dados sejam sempre válidos.

Regra de Ouro do VO: Ele é imutável e possui comportamento. Se você alterar o valor de um VO, você não o modifica; você cria uma nova instância com o novo valor. Além disso, ele se auto-valida no momento da criação.

Cenários ideais para VOs Link para o cabeçalho

  • Dados compostos com regras próprias: CPF/CNPJ, Endereços, Coordenadas Geográficas (Latitude/Longitude).
  • Unidades de medida e valores monetários: R$ 10,00 não é apenas o número 10, é o número 10 + a moeda BRL. Um VO de Dinheiro impede que você some 10 Reais com 5 Dólares sem uma conversão explícita.
  • Garantia de consistência: Evitar tipos primitivos genéricos (como usar uma string simples para um e-mail que precisa ser validado em todo o sistema).

Tabela Comparativa: DTO vs. VO Link para o cabeçalho

Característica DTO (Data Transfer Object) VO (Value Object)
Foco principal Otimizar e estruturar o transporte de dados. Representar um conceito e lógica de negócio.
Identidade Não possui. Não possui (identificado por seus valores).
Mutabilidade Geralmente mutável (pode ter setters), embora DTOs imutáveis sejam recomendados. Sempre imutável.
Lógica/Regras Nenhuma lógica de negócio. Apenas dados brutos. Contém validações e métodos de negócio relacionados ao valor.
Localização Camada de infraestrutura, aplicação ou entrega (API/Controladores). Camada de Domínio (Domain).

Exemplo Prático em PHP Link para o cabeçalho

Imagine um cenário de cadastro de usuários. Vamos receber os dados brutos da API usando um DTO e, em seguida, transformar o e-mail em um VO para garantir que ele seja válido de acordo com as regras de negócio.

Nota: O exemplo utiliza recursos do PHP 8.1+ (como propriedades readonly e promoted properties).

1. Criando o Value Object (Email) Link para o cabeçalho

O VO abaixo garante que nenhum e-mail inválido exista dentro do nosso domínio.

<?php

declare(strict_types=1);

class Email
{
    // Imutabilidade garantida pelo readonly
    public readonly string $value;

    public function __construct(string $value)
    {
        // O VO se auto-valida no construtor
        if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException("O e-mail '{$value}' não é um endereço válido.");
        }

        $this->value = $value;
    }

    // Exemplo de comportamento/lógica dentro do VO
    public function getDomain(): string
    {
        return substr(strrchr($this->value, "@"), 1);
    }
}

2. Criando o DTO (UsuarioCadastroDTO) Link para o cabeçalho

O DTO apenas agrupa os dados que vieram da requisição HTTP (HTTP Request). Ele não valida se o e-mail é real ou se a senha é forte, apenas guarda as strings.

<?php

declare(strict_types=1);

class UsuarioCadastroDTO
{
    // Apenas transporte de dados brutos
    public function __construct(
        public readonly string $nome,
        public readonly string $email,
        public readonly string $senha
    ) {}

    // Método auxiliar opcional para criar o DTO a partir de um array (ex: $_POST)
    public static function fromArray(array $dados): self
    {
        return new self(
            $dados['nome'] ?? '',
            $dados['email'] ?? '',
            $dados['senha'] ?? ''
        );
    }
}

3. Juntando tudo no Caso de Uso / Serviço Link para o cabeçalho

Veja como o Controlador recebe o DTO e o Serviço utiliza o VO para processar a lógica com segurança.

<?php

// Simulando os dados que vieram do POST da API
$dadosDaRequisicao = [
    'nome'  => 'John Doe',
    'email' => 'john.doe@email.com',
    'senha' => '123456'
];

// 1. O Controlador transforma a requisição em um DTO de forma limpa
$dto = UsuarioCadastroDTO::fromArray($dadosDaRequisicao);

// 2. O Serviço de Domínio processa o DTO
class CriarUsuarioService 
{
    public function executar(UsuarioCadastroDTO $dto): void
    {
        // Transformamos a string bruta do DTO em um Value Object seguro
        // Se o e-mail fosse inválido, o código quebraria aqui disparando a exceção do VO
        $emailValido = new Email($dto->email);
        
        echo "Criando o usuário: {$dto->nome}\n";
        echo "E-mail validado com sucesso: {$emailValido->value}\n";
        echo "Domínio do e-mail: {$emailValido->getDomain()}\n";
        
        // Daqui para frente, persistiria no banco de dados...
    }
}

// Executando o exemplo
$servico = new CriarUsuarioService();
$servico->executar($dto);

Conclusão Link para o cabeçalho

Embora ambos evitem que passemos arrays associativos gigantescos e confusos de um lado para o outro, lembre-se:

  • Use DTO quando precisar** mover dados** entre camadas ou sistemas sem aplicar regras sobre eles.
  • Use VO quando precisar dar significado aos dados e garantir que eles sigam regras estritas do seu negócio.