1. Découverte des DTO
DTO (Data Transfer Object, «objet de transfert de données») — c'est un terme qui vient du monde pro du dev, surtout dans les applis distribuées et les services (genre quand un service appelle un autre via le réseau). En gros, c'est juste un objet — la plupart du temps une classe C# toute simple — dont le seul taf c'est : contenir un ensemble de données et être transféré en toute sécurité entre les couches du programme ou entre applis.
Imagine un chat entre un client et un serveur. À chaque fois qu'un utilisateur envoie un message, le client crée un objet avec le texte, l'heure et le nom de l'utilisateur, l'envoie au serveur, et c'est le serveur qui décide quoi en faire. Ce serait cool que cet objet ne contienne rien de trop : juste ce qu'il faut pour le transfert. Voilà, c'est ça une classe DTO.
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; }
}
C'est tout ! Pas de logique métier, pas de méthodes compliquées — juste des propriétés.
Des classes juste pour transférer des données
Imagine que tu fais de la livraison de pizza. Ton livreur (DTO) n'a pas besoin de savoir cuisiner la pizza, prendre les commandes ou réparer le vélo. Tout ce qu'on lui demande, c'est d'apporter la boîte du point A au point B. Pareil pour une classe DTO : son taf, c'est juste de stocker des données simples. Elle n'a pas à se valider elle-même, ni à savoir comment s'afficher dans l'UI — juste stocker.
- Un DTO, c'est un «sans métier» ou un «objet sans logique», il fait juste passer les données.
- On les utilise pour éviter de polluer la logique métier avec les détails du transfert de données : plus la valise est petite, plus c'est facile à porter.
2. Les soucis des classes DTO classiques en pratique
On pourrait croire que c'est le bonheur : tu déclares une classe avec des auto-propriétés et c'est la belle vie ! Mais c'est pas si simple.
Mutabilité des données : qu'est-ce qui peut foirer
public class ProductDto
{
public int Id { get; set; }
public string Name { get; set; }
}
Maintenant, imagine qu'à un endroit du code, on change une propriété par erreur :
ProductDto dto = new ProductDto { Id = 1, Name = "Lait" };
SomeApiProcess(dto);
dto.Name = "Kéfir"; // Oups !
Comme toutes les propriétés ont un set public, c'est super facile de flinguer l'objet par accident ou, pire, de le modifier à un endroit et que ça foute le bazar partout dans le système. Ça devient vraiment un problème quand les objets sont utilisés par plein de modules et de threads.
Copier et comparer — la routine sans fin
Supposons que tu veuilles créer une copie de ProductDto, mais juste changer le nom.
var otherDto = new ProductDto { Id = dto.Id, Name = "Fromage blanc" };
S'il y a plein de propriétés, ça devient un process chiant et potentiellement source de bugs de tout recopier à la main.
Et si tu dois comparer les objets ? Il va falloir overrider Equals et GetHashCode, ce que beaucoup font à l'arrache ou oublient carrément.
L'immuabilité — pas pour les «simples» classes
Dans la plupart des cas, les DTO devraient être immuables : on reçoit les données une fois — et on ne les change plus. Mais une classe avec des auto-propriétés, c'est pas fait pour ça : n'importe qui peut modifier n'importe quel champ.
3. À quoi ça ressemble dans un gros projet
Voyons un exemple de la vraie vie.
T'es dev dans un énorme site e-commerce. Tu dois transférer des infos de commande sur le réseau. Pour ça, tu crées une classe comme ça :
public class OrderDto
{
public int Id { get; set; }
public string Customer { get; set; }
public double Total { get; set; }
}
Tout roule, jusqu'à ce qu'il y ait des centaines de milliers de commandes par semaine. Plein de microservices se baladent les DTO dans tous les sens, y'en a un qui oublie que Total doit être calculé et pas mis à la main, un autre qui modifie Customer après l'envoi… Et là, tu te retrouves avec des bugs super durs à choper.
Comment améliorer ça ?
- N'utiliser que des propriétés get (sans set).
- Créer un constructeur spécial pour fixer les valeurs seulement à la création.
- Override Equals et GetHashCode…
Tu vois où ça mène ? Ce qui devait être une simple «valise» devient un monstre chelou.
4. Petit résumé des problèmes typiques des classes DTO
Dans les petits projets, les bugs à cause de la modif d'un DTO, tu peux les «attraper à la main». Dans les gros — c'est la cata. Les DTO servent pour l'auth, le transfert d'argent, la gestion des commandes — toute modif illégale peut finir en perte de thunes ou (bordel !) en affaire pénale.
Voilà un «aide-mémoire» des problèmes que rencontrent quasi tous ceux qui font des DTO avec des classes :
| Problème | Description |
|---|---|
| Mutabilité | Les données sont faciles à modifier partout — risque de bugs (surtout en multi-thread) |
| Clonage | Copier les objets, c'est galère, souvent fait à la main |
| Comparaison | Par défaut, comparaison par référence, pas par valeur |
| Support des copies with | On aimerait copier en changeant juste une partie des données sans tout recopier à la main |
| Imbrication pas évidente | Les DTO imbriqués doivent être copiés entièrement, c'est relou |
5. Exemple : transfert de données entre les couches dans notre appli
Imaginons qu'on a une appli agenda où on peut stocker des tâches. Avant, on aurait eu une classe comme ça :
public class TaskDto
{
public int Id { get; set; }
public string Description { get; set; }
public DateTime DueDate { get; set; }
}
On lisait le TaskDto depuis un fichier, puis on l'ajoutait à une collection, puis on l'affichait à l'écran. Tout allait bien... jusqu'à ce que quelqu'un d'un autre module change par erreur le DueDate juste avant la sérialisation — et là, l'utilisateur est perdu.
7. On peut faire mieux ?
Oui ! Et pas seulement on peut, mais on doit ! Pour régler ces problèmes, C# a introduit une construction spéciale — record. Ça règle la plupart des prises de tête avec les classes DTO, presque comme par magie.
Ce que fait record :
- Par défaut, c'est orienté valeur : comparaison des objets par contenu, pas par référence.
- Immuabilité : tu peux mettre que des propriétés get, les valeurs sont fixées via le constructeur.
- Clonage facile : y'a la syntaxe with pour copier en modifiant.
- Génération auto des méthodes de comparaison et d'affichage.
public record TaskDto(int Id, string Description, DateTime DueDate);
Stylé, non ? Mais ça, c'est pour la prochaine leçon.
GO TO FULL VERSION