1. Conhecendo o DTO
DTO (Data Transfer Object, "objeto de transferência de dados") é um termo que veio do mundo da programação profissional, principalmente de apps e serviços distribuídos (tipo quando um serviço chama outro pela rede). No fim das contas, é só um objeto — na maioria das vezes uma classe C# comum — cuja única missão é: guardar um conjunto de dados e ser passado de forma segura entre camadas do programa ou entre apps.
Imagina um chat entre cliente e servidor. Toda vez que o usuário manda uma mensagem, o cliente monta um objeto com o texto, horário e nome do usuário, manda pro servidor, e aí o servidor decide o que fazer com isso. Seria ótimo se esse objeto não tivesse nada a mais: só o necessário pra transferir os dados. Isso aí é a ideia do DTO.
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; }
}
É isso! Nada de lógica de negócio, nada de métodos complicados — só propriedades.
Classes só pra carregar dados
Imagina que você trabalha com entrega de pizza. Seu entregador (DTO) não precisa saber fazer pizza, pegar pedido ou consertar bicicleta. Tudo que ele tem que fazer é levar a caixa do ponto A ao ponto B. O DTO é igual: a missão dele é guardar dados simples. Ele não precisa se validar, não precisa saber como se mostrar na UI — só guardar.
- DTO é tipo "pessoa sem profissão" ou "objeto sem lógica", só carrega dados mesmo.
- Usam eles pra não encher a lógica de negócio com detalhes de transferência de dados: quanto menor a mala, mais fácil de carregar.
2. Problemas dos DTOs comuns na prática
Parece perfeito: declarou a classe com auto-propriedades e vida que segue! Mas não é tão simples assim.
Mutabilidade dos dados: o que pode dar ruim
public class ProductDto
{
public int Id { get; set; }
public string Name { get; set; }
}
Agora imagina que em algum lugar do código alguém muda uma propriedade sem querer:
ProductDto dto = new ProductDto { Id = 1, Name = "Leite" };
SomeApiProcess(dto);
dto.Name = "Iogurte"; // Opa!
Como todas as propriedades têm set público, é fácil estragar o objeto sem querer ou, pior, mudar ele num lugar e isso se espalhar pelo sistema todo. Isso vira um problemão quando os objetos são usados por vários módulos e threads.
Cópia e comparação — rotina chata sem fim
Digamos que você quer criar uma cópia do ProductDto, mas só mudar o nome.
var otherDto = new ProductDto { Id = dto.Id, Name = "Queijo" };
Se tiver muita propriedade, isso vira um processo chato e cheio de chance de bug, copiando campo por campo na mão.
E se você ainda precisar comparar objetos? Vai ter que sobrescrever Equals e GetHashCode, que muita gente faz de qualquer jeito ou até esquece.
Imutabilidade — não rola com classe "simples"
Na maioria dos casos, DTO deveria ser imutável: pegou os dados uma vez — não muda mais. Mas classe com auto-propriedades não serve pra isso: qualquer um pode mudar qualquer campo.
3. Como isso aparece na prática num projeto grande
Vamos ver um exemplo da vida real.
Você é dev num mega e-commerce. Precisa mandar info do pedido pela rede. Pra isso, cria uma classe assim:
public class OrderDto
{
public int Id { get; set; }
public string Customer { get; set; }
public double Total { get; set; }
}
Tudo funciona, até que aparecem centenas de milhares de pedidos por semana. Um monte de microserviços começa a passar DTO pra lá e pra cá, alguém esquece que Total tem que ser calculado, não setado na mão, alguém muda Customer depois de enviar... E aí começam os bugs que são um inferno de achar.
Como dá pra melhorar?
- Usar só propriedades com get (sem set).
- Fazer um construtor especial pra setar os valores só na criação.
- Sobrescrever Equals e GetHashCode...
Sacou onde isso vai parar? O que era pra ser uma "malinha" simples, de repente vira um monstrão confuso.
4. Resumão dos problemas típicos dos DTOs
Em projetos pequenos, bugs causados por mudar DTO até dá pra "pegar no olho". Em projetos grandes — é desastre. DTO é usado pra autorização, transferência de grana, processamento de pedidos — qualquer alteração ilegal pode virar prejuízo ou (puta merda!) até caso de polícia.
Aqui vai um "resumão" dos problemas que quase todo mundo que escreve DTO com classe já passou:
| Problema | Descrição |
|---|---|
| Mutabilidade | Dá pra mudar os dados em qualquer lugar — risco de bug (principalmente em multithread) |
| Clonagem | Copiar objetos é chato, geralmente fazem na mão |
| Comparação | Por padrão compara por referência, não por valor |
| Suporte a with-cópias | Seria massa copiar mudando só parte dos dados sem copiar tudo na mão |
| Aninhamento não óbvio | DTOs aninhados têm que ser copiados por inteiro, o que é chato |
5. Exemplo: transferência de dados entre camadas no nosso app
Digamos que temos um app de agenda, onde dá pra guardar tarefas. Antes, a classe seria assim:
public class TaskDto
{
public int Id { get; set; }
public string Description { get; set; }
public DateTime DueDate { get; set; }
}
A gente lia o TaskDto de um arquivo, depois jogava ele numa coleção, depois mostrava na tela. Tudo certo... até alguém de outro módulo mudar o DueDate sem querer antes de serializar — e aí o usuário ficava perdido.
7. Dá pra fazer melhor?
Com certeza! E não só dá, como deve! Pra resolver esses problemas, o C# trouxe uma parada especial — record. Ele resolve quase toda a dor de cabeça dos DTOs, tipo mágica.
O que o record faz:
- Por padrão é orientado a valor: compara objetos pelo conteúdo, não pela referência.
- Imutabilidade: dá pra colocar só propriedades com get, valores definidos no construtor.
- Cópia fácil: tem a construção with pra copiar mudando só o que quiser.
- Gera métodos de comparação e toString automático.
public record TaskDto(int Id, string Description, DateTime DueDate);
Massa, né? Muito! Mas isso é papo pra próxima aula.
GO TO FULL VERSION