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úmero10+ a moedaBRL. Um VO deDinheiroimpede 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
stringsimples 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.