Les API RESTful ont-elles tendance à encourager les modèles de domaine anémique?

34

Je travaille sur un projet dans lequel nous essayons d'appliquer à la fois une conception orientée domaine et REST à une architecture orientée service. Nous ne nous inquiétons pas de la conformité 100% REST; il serait probablement préférable de dire que nous essayons de créer des API HTTP orientées ressources (~ niveau 2 du modèle de maturité REST de Richardson). Néanmoins, nous essayons d'éviter l'utilisation de requêtes HTTP de type RPC, c'est-à-dire que nous essayons d'implémenter nos verbes HTTP conformément à la norme RFC2616 plutôt que d'utiliser la méthode POSTà faire IsPostalAddressValid(...), par exemple.

Cependant, mettre l'accent sur cela semble se faire aux dépens de notre tentative d'appliquer une conception pilotée par domaine. Avec seulement GET, POST, PUT, DELETEet quelques autres méthodes rarement utilisées, nous avons tendance à construire des services Cruddy et services Cruddy ont tendance à avoir des modèles anémiques de domaine.

POST: Recevoir les données, les valider, les transférer dans les données. GET: Récupérer les données, les renvoyer. Pas de vraie logique métier là-bas. Nous utilisons également des messages (événements) entre les services, et il me semble que l'essentiel de la logique métier finit par être construit autour de cela.

REST et DDD sont-ils en tension à un certain niveau? (Ou est-ce que je comprends mal quelque chose ici? Peut-être faisons-nous autre chose de mal?) Est-il possible de construire un modèle de domaine fort dans une architecture orientée service tout en évitant les appels HTTP de type RPC?

Kazark
la source
1
POST a été délibérément conçu pour être "intentionnellement vague"; le résultat d'un POST est spécifique à l'implémentation. Qu'est-ce qui vous empêche de faire ce que font Twitter et les autres concepteurs d'API et de définir chaque méthode POST dans la partie non-CRUD de votre API en fonction de vos besoins spécifiques?
Robert Harvey
@ RobertHarvey Nous avons conçu POST comme une création. En revoyant la norme , c'est peut-être trop simpliste. Par exemple, croyez-vous que POST IsPostalAddressValid(...)pourrait s’insérer dans "Fournir un bloc de données, tel que le résultat de la soumission d’un formulaire, à un processus de traitement de données"?
Kazark
C'est parce qu'il n'y a pas de verbe CREATE (ni de verbe UPDATE, d'ailleurs). Je maintiens que ces verbes sont absents de la norme (quelle que soit la raison), ce qui explique pourquoi vous devez coopter pour POST pour "tout le reste". Dans ce cas, votre "processus de traitement des données" est le processus qui examine l'adresse postale et renvoie une valeur correspondant au résultat de cette analyse.
Robert Harvey
1
@RobertHarvey: Je pense que POST et PUT / PATCH est simplement le verbe CREATE et UPDATE que vous avez manqué. Il n'est nommé que différemment, de sorte que le verbe a toujours un sens, même dans une conception non-RESTful.
Lie Ryan
@LieRyan: Je vous l'accorde. Je pense simplement que CRUD implique des modèles de données anémiques par définition. Vous pouvez conserver certains comportements si, par exemple, vous êtes dans le M de MVC, mais certainement pas à travers des systèmes hétérogènes. Pour tout le reste sauf CRUD, vous avez besoin de POST.
Robert Harvey

Réponses:

38

Première loi de Martin Fowler sur les systèmes distribués: "Ne distribuez pas vos objets!" Les interfaces distantes doivent être à grain grossier et les interfaces internes à grain fin. Souvent, le modèle de domaine riche s'applique uniquement dans un contexte limité .

L'API REST sépare deux contextes différents ayant chacun leur propre modèle interne. Les contextes communiquent via une interface à grain grossier (API REST) ​​utilisant des objets "anémiques" (DTO).

Dans votre cas, il semble que vous tentiez de répartir un contexte sur une limite qui est une API REST. Cela peut conduire à une interface distante fine ou à un modèle anémique. En fonction de votre projet, cela peut poser un problème ou non.

simoraman
la source
1
Fowler a beaucoup de bonnes pensées mais n'oublions pas à quel point les spécifications et les implémentations d'origine des EJB étaient un désastre. Ce n'est qu'après qu'ils ont compris que les appels de méthode de bas niveau pour chaque opération mineure telle que getName () étaient un cauchemar réseau / chargement. Les interfaces à grain grossier sont devenues la voie à suivre et le concept selon lequel des graphes / messages d'entité entiers étaient envoyés et reçus dans le contexte verbe + nom.
Darrell Teague
9

POST a été délibérément conçu pour être "intentionnellement vague"; le résultat d'un POST est spécifique à l'implémentation. Qu'est-ce qui vous empêche de faire ce que font Twitter et les autres concepteurs d'API et de définir chaque méthode POST dans la partie non-CRUD de votre API en fonction de vos besoins spécifiques? POST est le verbe catchall. Utilisez-le lorsqu'aucun des autres verbes ne convient parfaitement à l'opération que vous souhaitez effectuer.

En d'autres termes, votre question pourrait également être posée comme suit: "Les objets" intelligents "encouragent-ils la conception de style RPC?" Même Martin Fowler (qui a inventé le terme "Modèle de domaine anémique") admet que les DTO nus présentent certains avantages:

Le comportement dans les objets de domaine ne doit pas être en contradiction avec la solide approche consistant à utiliser la superposition pour séparer la logique de domaine de choses telles que la persistance et les responsabilités de présentation. La logique qui devrait figurer dans un objet de domaine est la logique de domaine - validations, calculs, règles de gestion - comme vous voulez l'appeler.

En ce qui concerne le modèle de maturité Richardson , vous pouvez accéder au niveau 3 sans vous préoccuper des "modèles de domaine anémique". N'oubliez pas que vous ne transférerez jamais le comportement sur le navigateur (à moins que vous n'ayez l'intention d'injecter du code JavaScript dans vos modèles).

REST concerne principalement l’indépendance de la machine; implémentez le modèle REST dans la mesure où vous souhaitez que vos points de terminaison représentent des ressources et que les utilisateurs de votre API puissent facilement accéder à ces ressources et les gérer de manière standard. Si cela semble anémique, alors qu'il en soit ainsi.

Voir aussi
j'ai besoin de plus de verbes

Robert Harvey
la source
Je pense que cela répond certainement au côté RFC2616 de la question. Qu'en est-il du fait que nous essayons d'être axés sur les ressources, c'est-à-dire d'essayer au moins d'atteindre le niveau 2 du modèle de maturité de Richardson pour REST?
Kazark
1
J'ai lu martinfowler.com/articles/richardsonMaturityModel.html . Vous pouvez accéder au niveau 3 sans vous préoccuper des "modèles de domaine anémique". N'oubliez pas que vous ne transférerez jamais le comportement sur le navigateur (à moins que vous n'ayez l'intention d'injecter du code JavaScript dans vos modèles).
Robert Harvey
4

L'API REST n'est qu'un type de couche de présentation. Cela n'a rien à voir avec le modèle de domaine.

La question que vous avez posée découle de votre confusion: vous devez vous adapter les uns aux autres. Vous pas.

Vous mappez votre modèle de domaine sur votre API REST de la même manière que vous mappez votre modèle de domaine sur un SGBDR via un ORM - cette couche de mappage doit exister.

Domaine ← ORM →
Domaine RDBMS ← Cartographie REST → API REST

Elnur Abdurrakhimov
la source
3

IMHO Je ne pense pas qu'ils ont tendance à encourager les modèles de domaine anémique (ADM), mais ils vous obligent à prendre un peu de temps et à réfléchir.

Tout d’abord, je pense que la principale caractéristique des SMA est qu’ils ont peu ou pas de comportement. Cela ne veut pas dire que le système n'a pas de comportement, mais qu'il se trouve généralement dans une sorte de classe de service (voir http://vimeo.com/43598193 ).

Et bien sûr, si le comportement n'existe pas dans ADM, que fait-on alors? La réponse est bien sûr les données. Et alors, comment cela mappe-t-il à l'API REST? On peut supposer que les données correspondent au contenu de la ressource et le comportement aux verbes HTTP.

Ainsi, vous disposez de tout ce dont vous avez besoin pour créer un modèle de domaine riche. Vous devez simplement savoir comment les verbes HTTP mappent les opérations de domaine sur les données, puis placez ces opérations dans les mêmes classes qui encapsulent vos données.

Je pense que les personnes qui rencontrent des problèmes ont tendance à avoir du mal à comprendre comment les verbes HTTP correspondent au comportement de leur domaine lorsque le comportement dépasse le simple CRUD, c'est-à-dire lorsqu'il y a des effets secondaires dans d'autres parties du domaine au-delà du domaine. ressource en cours de modification par la requête HTTP. Un moyen de résoudre ce problème consiste à utiliser des événements de domaine ( http://www.udidahan.com/2009/06/14/domain-events-salvation/ ).

RibaldEddie
la source
3

Cet article est assez lié au sujet et je crois que répond à votre question.

Un concept de base qui, je pense, répond très bien à votre question, est résumé dans le paragraphe suivant de l'article mentionné:

"Il est très important de faire la distinction entre les ressources de l'API REST et les entités de domaine dans une conception pilotée par le domaine. La conception pilotée par le domaine s'applique à la mise en œuvre (y compris l'implémentation d'API), tandis que les ressources de l'API REST pilotent la conception et le contrat de l'API. Ressource API la sélection ne doit pas dépendre des détails de la mise en œuvre du domaine sous-jacent. "

Majix
la source
1

Plusieurs implémentations raisonnablement réussies que j'ai vues / construites répondent à la question de savoir comment elles mélangent la métaphore verbe + nom en utilisant des méthodes «conviviales pour les entreprises» à grain grossier qui agissent sur les entités.

Ainsi, au lieu de la getName()méthode / du service (condamné) , exposez getPerson()en transmettant des éléments tels que identificateur-type / ID et en renvoyant l' Personentité entière .

Dans la mesure où les comportements de l'entité Personne dans un tel contexte ne peuvent pas être correctement communiqués (ni peut-être dans un contexte centré sur les données), il est parfaitement raisonnable de définir un modèle de données (par rapport à un objet) pour les paires requête / réponse. les services.

Les services et les verbes définis eux-mêmes ajouteront des comportements, des contrôles et même des règles de transition d'état autorisés par le domaine pour les entités. Par exemple, il y aurait une logique spécifique à un domaine pour ce qui se passe dans l' transferPerson()appel de service, mais l'interface elle-même ne définirait que les entités / données d'entrée / sortie sans définir LEUR comportement interne.

Je suis en désaccord avec les auteurs qui diraient, par exemple, qu'une implémentation de verbe de transfert appartient à la classe Personne ou associée à un service centré sur la personne. En fait, la méthode de transfert pour a Personet ses options (dans cet exemple simple) seraient mieux définies par un Carrier, dans lequel Personils ne savent même pas quelles méthodes de transfert sont disponibles ni comment se produit le transfert (qui sait comment les réacteurs fonctionnent en tous cas).

Cela rend-il l' Personentité anémique? Je ne pense pas.

Il peut / devrait y avoir une logique à propos de choses propres à une personne qui sont internes à une personne, comme son état de santé, qui ne devrait pas être défini par une classe externe.

Toutefois, selon les cas d'utilisation, il est tout à fait acceptable qu'une classe d'entités ne présente aucun comportement important / pertinent dans certains systèmes, tel qu'un service d'attribution de siège dans un système de transport. Un tel système peut très bien implémenter des services basés sur REST qui traitent des instances Person et des identifiants associés, mais ne définissent / implémentent jamais leurs comportements internes.

Darrell Teague
la source
Bons points - cela apporte vraiment une perspective nouvelle que d'autres réponses n'avaient pas encore.
Kazark
0

Est-ce que votre problème est que vous essayez d'inscrire votre modèle dans le jeu de verbes de base, en utilisant autant que possible le POST?

Ce n'est pas nécessaire - je sais que pour la plupart des gens, REST signifie POST, GET, PUT et DELETE, mais le http rfc indique:

L'ensemble des méthodes courantes pour HTTP / 1.1 est défini ci-dessous. Bien que cet ensemble puisse être développé, des méthodes supplémentaires ne peuvent pas être supposées partager la même sémantique pour des clients et des serveurs étendus séparément.

Et des systèmes tels que SMTP utilisent le même style de méthodes à base de verbe, mais avec un ensemble totalement différent.

Donc, il n'y a aucune raison pour que vous utilisiez ceux-ci, vous pouvez utiliser le jeu de verbes que vous préférez (cependant, vous constaterez que vous pouvez faire tout ce dont vous avez besoin dans le 4 de base avec un peu de réflexion). Ce qui distingue REST des autres mécanismes, c’est sa manière apatride et cohérente de mettre en œuvre ces verbes. Vous ne devez pas essayer d’implémenter le système de transmission de messages entre les niveaux car vous n’effectuez pas fondamentalement REST, vous utilisez plutôt un mécanisme de transmission de message, RPC ou de file d’attente de messages qui vous fera certainement perdre les avantages de REST simplicité qui fait que cela fonctionne vraiment bien sur une connexion http).

Si vous voulez un protocole de messagerie complexe et complet, construisez-le (si vous pouvez le faire sur le Web, il existe une raison pour laquelle REST est si populaire), mais sinon, essayez de vous en tenir à la conception architecturale de REST.

gbjbaanb
la source