web-dev-qa-db-fra.com

Microservices Restful API - DTO ou pas?

API REST - DTO ou pas?

Je voudrais poser à nouveau cette question dans le contexte des microservices. Voici la citation de la question d'origine.

Je suis en train de créer une API REST pour un projet et j'ai lu article après article sur les meilleures pratiques. Beaucoup semblent être contre les DTO et exposent simplement le modèle de domaine, tandis que d'autres semblent penser que les DTO (ou les modèles utilisateur ou tout ce que vous voulez appeler) sont de mauvaises pratiques. Personnellement, je pensais que cet article avait beaucoup de sens.

Cependant, je comprends également les inconvénients des DTO avec tout le code de mappage supplémentaire, des modèles de domaine qui pourraient être 100% identiques à leur homologue DTO, etc.

Maintenant, ma question

Je suis plus aligné sur l'utilisation d'un objet à travers toutes les couches de mon application (en d'autres termes, il suffit d'exposer l'objet de domaine plutôt que de créer DTO et de copier manuellement sur chaque champ). Et les différences dans mon contrat par rapport au code peuvent être corrigées en utilisant des annotations Jackson comme @JsonIgnore Ou @JsonProperty(access = Access.WRITE_ONLY) ou @JsonView Etc.). Ou s'il y a un ou deux champs qui nécessitent une transformation qui ne peut pas être effectuée à l'aide de Jackson Annotation, alors j'écrirai une logique personnalisée pour gérer exactement cela (croyez-moi, je n'ai pas rencontré ce scénario pas même une fois dans mes 5 ans et plus) long voyage dans les services de repos)

Je voudrais savoir si je manque de vrais effets négatifs pour ne pas copier le domaine vers DTO

18
so-random-dude

Les avantages de simplement exposer des objets de domaine

  1. Moins vous écrivez de code, moins vous produisez de bogues.
    • en dépit d'avoir des cas de test étendus (discutables) dans notre base de code, j'ai rencontré des bogues en raison de la copie manquée/incorrecte des champs du domaine vers le DTO ou vice versa.
  2. Maintenabilité - Moins de code de plaque de chaudière.
    • Si je dois ajouter un nouvel attribut, je n'ai pas besoin d'ajouter dans Domain, DTO, Mapper et les testcases, bien sûr. Ne me dites pas que cela peut être réalisé en utilisant une réflexion beanCopy utils, cela va à l'encontre du but.
    • Lombok, Groovy, Kotlin, je sais, mais cela ne me sauvera que des maux de tête de getter setter.
  3. DRY
  4. Performance
    • Je sais que cela relève de la catégorie "l'optimisation prématurée des performances est la racine de tout mal". Mais cela économisera quand même certains cycles de processeur pour ne pas avoir à créer (et plus tard le ramasse-miettes) un objet de plus (au moins) par demande

Les inconvénients

  1. Les DTO vous donneront plus de flexibilité à long terme
    • Si seulement j'avais besoin de cette flexibilité. Au moins, tout ce que j'ai rencontré jusqu'à présent, ce sont des opérations CRUD sur http que je peux gérer en utilisant quelques @JsonIgnores. Ou s'il y a un ou deux champs qui nécessitent une transformation qui ne peut pas être effectuée à l'aide de Jackson Annotation, comme je l'ai dit plus tôt, je peux écrire une logique personnalisée pour gérer exactement cela.
  2. Objets de domaine gonflés d'annotations.
    • C'est une préoccupation valable. Si j'utilise JPA ou MyBatis comme framework persistant, l'objet de domaine peut avoir ces annotations, alors il y aura aussi des annotations Jackson. Dans mon cas, ce n'est pas très applicable, j'utilise Spring Boot et je peux m'en tirer en utilisant des propriétés à l'échelle de l'application comme mybatis.configuration.map-underscore-to-camel-case: true, spring.jackson.property-naming-strategy: SNAKE_CASE

Petite histoire, au moins dans mon cas, les inconvénients ne l'emportent pas sur les avantages, donc cela n'a aucun sens de me répéter en ayant un nouveau POJO comme DTO. Moins de code, moins de risques de bugs. Donc, aller de l'avant avec l'exposition de l'objet Domain et ne pas avoir d'objet "view" séparé.

Clause de non-responsabilité: Cela peut ou non s'appliquer à votre cas d'utilisation. Cette observation est selon mon cas d'utilisation (essentiellement une API CRUD ayant 15 points d'extrémité)

6
so-random-dude

Je voterais pour l'utilisation des DTO et voici pourquoi:

  • Différentes requêtes (événements) et vos entités de base de données . Il arrive souvent que vos demandes/réponses soient différentes de celles que vous avez dans le modèle de domaine. En particulier, cela a du sens dans l'architecture de microservices, où vous avez beaucoup d'événements provenant d'autres microservices. Par exemple, vous avez une entité Order, mais l'événement que vous obtenez d'un autre microservice est OrderItemAdded. Même si la moitié des événements (ou demandes) sont les mêmes que des entités, il est toujours logique d'avoir un DTO pour chacun d'eux afin d'éviter un gâchis.
  • Couplage entre le schéma DB et l'API que vous exposez . Lorsque vous utilisez des entités, vous exposez essentiellement la façon dont vous modélisez votre base de données dans un microservice particulier. Dans MySQL, vous voudrez probablement que vos entités aient des relations, elles seront assez massives en termes de composition. Dans d'autres types de bases de données, vous auriez des entités plates sans beaucoup d'objets internes. Cela signifie que si vous utilisez des entités pour exposer votre API et que vous voulez changer votre base de données, disons MySQL en Cassandra - vous devrez également changer votre API, ce qui est évidemment une mauvaise chose à avoir.
  • Contrats axés sur le consommateur . Cela est probablement lié à la puce précédente, mais les DTO permettent de s'assurer plus facilement que la communication entre les microservices n'est pas interrompue pendant leur évolution. Parce que les contrats et la base de données ne sont pas couplés, cela est simplement plus facile à tester.
  • Agrégation . Parfois, vous devez retourner plus que vous n'en avez dans une seule entité DB. Dans ce cas, votre DTO ne sera qu'un agrégateur.
  • Performances . Les microservices impliquent beaucoup de transfert de données sur le réseau, ce qui peut vous coûter des problèmes de performances. Si les clients de votre microservice ont besoin de moins de données que vous n'en stockez dans la base de données - vous devez leur fournir moins de données. Encore une fois - faites simplement un DTO et votre charge réseau sera réduite.
  • Oubliez LazyInitializationException. Les DTO n'ont pas de chargement et de proxy paresseux contrairement aux entités de domaine gérées par votre ORM.
  • La couche DTO n'est pas si difficile à prendre en charge avec les bons outils. Habituellement, il y a un problème lors du mappage d'entités en DTO et en arrière - vous devez définir les bons champs manuellement chaque fois que vous souhaitez effectuer une conversion. Il est facile d'oublier de définir le mappage lors de l'ajout de nouveaux champs à l'entité et au DTO, mais heureusement, il existe de nombreux outils qui peuvent effectuer cette tâche pour vous. Par exemple, nous avions auparavant MapStruct sur notre projet - il peut générer une conversion pour vous automatiquement et en temps de compilation .
21
Danylo Zatorsky

La décision est beaucoup plus simple si vous utilisez CQRS car:

  • pour l'écriture, vous utilisez Commands qui sont déjà des DTO; Aggregates - les objets à comportement riche de votre couche de domaine - ne sont pas exposés/interrogés, il n'y a donc aucun problème.
  • pour le côté lecture, car vous utilisez une couche mince, les objets extraits de la persistance doivent déjà être des DTO. Il ne devrait pas y avoir de problème de mappage car vous pouvez avoir un readmodel pour chaque cas d'utilisation. Dans le pire des cas, vous pouvez utiliser quelque chose comme GraphQL pour sélectionner uniquement les champs dont vous avez besoin.

Si vous ne divisez pas la lecture de l'écriture, la décision est plus difficile à prendre car il existe des compromis dans les deux solutions.

2
Constantin Galbenu