DTO, entity e value object: qual é a diferença e quando usar cada um
Tratar DTO, entity e value object como sinônimos é a origem de modelos ruins. Entenda o que define cada padrão e onde a maioria dos projetos erra.
Em quase todo code review de domínio que faço, encontro a mesma confusão: alguém criou uma classe chamada UserDTO que tem regra de negócio dentro, ou uma entity User que é serializada direto na resposta da API, ou um CPF representado como string solto perambulando por dez métodos diferentes. São três conceitos distintos — DTO, entity e value object — e tratá-los como sinônimos é a origem de boa parte dos modelos ruins que vejo. Cada um resolve um problema específico, e quando você os mistura, paga o preço em acoplamento, validação espalhada e bugs sutis. Este texto separa os três com precisão e diz quando usar cada um.
O foco aqui é conceitual e prático: o que define cada padrão, por que eles não são intercambiáveis, e onde a maioria dos projetos erra.
Entity: o que tem identidade
Uma entity é um objeto que tem identidade própria e contínua ao longo do tempo. O traço definidor não são os atributos, é o identificador. Um User com id = 42 continua sendo o mesmo user mesmo que mude de nome, de e-mail e de endereço. E o contrário também vale: dois users com exatamente o mesmo nome, mesmo e-mail e mesma data de nascimento são pessoas diferentes se os ids forem diferentes.
Isso muda como você compara. Igualdade de entity é igualdade de identidade, não de valores:
class User:
def __init__(self, id, name, email):
self.id = id
self.name = name
self.email = email
def __eq__(self, other):
return isinstance(other, User) and self.id == other.id
Entities são mutáveis por natureza — elas representam algo que tem ciclo de vida no domínio. Um pedido nasce, é pago, é enviado, é entregue: o mesmo Order, atravessando estados. Entities vivem no coração do domínio e carregam comportamento, não só dados. É nelas que mora a regra de negócio que muda estado.
Value object: o que é definido pelos valores
Um value object é o oposto exato no critério de igualdade. Ele não tem identidade — é definido inteiramente pelos seus valores. Dinheiro, Endereço, CPF, intervalo de datas, coordenada geográfica: todos são value objects. Duas instâncias com os mesmos valores não são apenas iguais, elas são a mesma coisa. R$ 10,00 é R$ 10,00, não importa qual objeto Money os carrega.
from dataclasses import dataclass
@dataclass(frozen=True)
class Money:
amount: int # centavos
currency: str
def __post_init__(self):
if self.amount < 0:
raise ValueError("Money nao pode ser negativo")
Repare em duas coisas. Primeiro, frozen=True: value objects devem ser imutáveis. Se você precisa de "outro" valor, cria uma nova instância — somar R$ 5,00 a R$ 10,00 retorna um novo Money de R$ 15,00, não muta o original. Imutabilidade elimina uma classe inteira de bugs de aliasing. Segundo, a validação no construtor: um value object é o lugar ideal para encapsular regra de validação. Se um Money negativo é inválido, ele simplesmente não pode existir — a regra fica em um lugar só, não espalhada por toda chamada que manipula valores.
Essa é minha defesa mais forte neste texto: use value objects de verdade. A maioria dos projetos sofre de "primitive obsession" — CPF como string, dinheiro como float, e-mail como string —, e o resultado é validação repetida em cada borda do sistema, mais a chance de passar um e-mail onde se esperava um CPF porque ambos são string. Um Cpf que só aceita valores válidos no construtor torna estados inválidos irrepresentáveis. Isso vale muito mais do que parece.
DTO: o que só transporta dados
Um DTO (Data Transfer Object) é um saco de dados sem comportamento, cujo único trabalho é transportar informação entre camadas ou processos. O exemplo canônico é o JSON que entra e sai da sua API. Um DTO não valida regra de negócio, não calcula nada, não tem identidade — ele só carrega campos.
@dataclass
class CreateUserRequest: # DTO de entrada
name: str
email: str
@dataclass
class UserResponse: # DTO de saida
id: int
name: str
O papel do DTO é desacoplar o contrato externo do modelo interno. A forma como você expõe dados na API não precisa — e não deveria — ser a forma como você os modela no domínio.
Por que não expor a entity direto na API
A tentação é grande: a entity User já tem id, name, email, então por que não serializá-la direto na resposta? Por dois motivos concretos.
O primeiro é acoplamento. No instante em que sua entity vira o contrato da API, qualquer mudança no domínio vira uma mudança de contrato público. Renomeou um campo internamente? Quebrou todos os clientes. Adicionou um campo que não deveria ser público? Vazou para o mundo.
O segundo é vazamento de dados. Entities frequentemente carregam coisas que não devem sair: hash de senha, flags internas, referências a outras entities que disparam carregamento em cascata. Serializar a entity inteira é como deixar a porta dos fundos aberta. O DTO é a fronteira deliberada: você escolhe, campo a campo, o que cruza.
DTO separado da entity: a duplicação que vale a pena
Aqui vem a objeção que sempre ouço: "mas o UserResponse é quase idêntico à entity User, isso é só duplicação". Sim, no começo parece. E mesmo assim, mantenha o DTO separado da entity.
O motivo é que contrato externo e modelo de domínio evoluem por forças diferentes. O domínio muda quando a regra de negócio muda. O contrato muda quando o cliente precisa de algo diferente. Vão divergir — é questão de quando, não de se. No dia em que a API precisar expor um campo calculado que não existe no domínio, ou esconder um que existe, ou achatar uma estrutura aninhada, você vai agradecer por ter a camada de tradução pronta em vez de ter que rasgar a entity. A "duplicação" é desacoplamento pago adiantado.
Um detalhe prático que economiza erro: ao definir um DTO a partir de um JSON real da API, gerar os tipos automaticamente com uma ferramenta como o conversor de JSON para TypeScript evita campos errados e tipos divergentes entre o contrato e o código. E se você quer ver onde DTOs, entities e value objects se encaixam no fluxo de uma requisição, o artigo sobre controller, service e repository mostra as camadas onde cada um circula.
Perguntas frequentes
DTO e value object são a mesma coisa?
Não, e confundi-los é comum. Ambos podem ser imutáveis e sem identidade, mas o propósito difere completamente. Um value object encapsula regra de domínio e validação — um Money que rejeita valores negativos. Um DTO é burro de propósito: só transporta dados entre camadas, sem validar nada de domínio. Um pode até virar matéria-prima do outro, mas eles vivem em mundos diferentes.
Posso usar a mesma classe como entity e DTO?
Tecnicamente sim, na prática evite. Funciona enquanto o contrato da API e o modelo de domínio forem idênticos, mas no momento em que divergem — e divergem — você fica preso. Ou polui a entity com preocupações de serialização, ou distorce a API para acompanhar o domínio. Manter separado parece duplicação no dia um e vira liberdade no dia cem.
Toda string deveria virar um value object?
Não toda, mas mais do que você usa hoje. Se um valor tem regra (CPF tem formato e dígito verificador, e-mail tem estrutura, dinheiro tem moeda e não pode ser negativo), ele merece um value object. Se é texto livre sem regra, como um comentário, string basta. O sinal de alerta é validação repetida: se você valida o mesmo formato em três lugares, falta um value object.
Value object precisa mesmo ser imutável?
Idealmente sim. A imutabilidade é o que garante que dois value objects iguais permaneçam intercambiáveis e que ninguém mute um valor compartilhado por baixo dos panos. Linguagens dão suporte direto — frozen=True em Python, record em Java/C#, readonly em TypeScript. Mutar um value object derrota o propósito; se você precisa de outro valor, crie uma instância nova.
O que levar deste texto
Os três padrões respondem a perguntas diferentes. Entity: "isto tem identidade que persiste no tempo?" Value object: "isto é definido só pelos seus valores e carrega regra de validação?" DTO: "isto só precisa atravessar uma fronteira sem comportamento?" Acerte a pergunta e o padrão se escolhe sozinho. Os dois investimentos que mais pagam: usar value objects de verdade para matar a primitive obsession e centralizar validação, e manter o DTO separado da entity mesmo quando parecer duplicação boba. O dia em que contrato e domínio divergirem — e esse dia chega — você vai estar do lado certo da decisão.
- 01 Portfólio de desenvolvedor: o que colocar (e o que cortar) Recrutador olha seu portfólio por vinte segundos. Um projeto terminado e no ar vence dez clones de tutorial. O que incluir, o que cortar e por que o README é metade da impressão.
- 02 Nubank Croma: o que vem no plano, quanto custa e pra quem realmente compensa O Nubank lançou o Croma, um plano de média renda entre o cartão gratuito e o Ultravioleta. Veja o que está incluído, o cashback real, os R$ 39 de mensalidade (e como zerar) e faça a conta antes de aderir.