LuccaSA / RestDrivenDomain

8 stars 1 forks source link

Rdd : Spécifications #258

Open nfaugout-lucca opened 5 years ago

nfaugout-lucca commented 5 years ago

Je vais décrire le fonctionnement de Rdd tel que je le vois dans l'idéal. Ensuite vous me direz ce que vous en pensez. Et une fois qu'on sera alignés, on verra en quoi il est différent de Rdd v3 et on saura alors quoi refactorer !

Architecture en couches

Rdd possède 4 couches qui ont chacune des responsabilités bien différentes :

Réponses HTTP

Rdd supporte les 4 verbes HTTP, et fait même une distinction sur le GET entre un GET vers une collection ou vers une entité en particulier.

Les controllers web exposent donc 5 routes (Get, GetById, Put, Post, Delete). Ils n'ont pas vocation à en exposer plus. Si tel est le cas, cela signifie qu'il faut créer une sous entité pour matérialiser le process qui se dessine.

Get renverra une 404 uniquement si le type d'entité n'existe pas (route inexistante).

GetById renverra une 404 si l'id ne correspond à aucun entité existante. Si une entité à été supprimée, alors son URL devra renvoyer une 404. Si en revanche elle est inactive, càd qu'on peut encore y accéder en passant un paramètre spécifique à la collection, alors son URL devra renvoyer une 200.

L'utilisation de Put n'est pas conseillée. Put ne doit être utilisé qu'en cas de modification de l'état d'une entité n'entraînant pas un processus métier identifié. Dans le cas contraire, il faut alors créer une nouvelle entité qui représentera ce processus métier et faire un Post sur cette nouvelle entité.

Put prend en entrée des clés/valeurs correspondant aux propriétés de l'entité, et renvoie en sortie l'entité complète dans son nouvel état.

Post permet de créer une entité. Actuellement il impose d'envoyer des clés/valeurs correspondant aux propriétés de l'entité. On pourrait imaginer qu'il soit bindé aux constructeurs de l'entité, et donc qu'on doivent envoyer des clés/valeurs correspondant aux paramètres des différents contructeurs. Ainsi on pourrait à la fois ouvrir un peu les contraintes en acceptant des paramètres ne correspondant pas systématiquement à des propriétés de l'entité, mais également renforcer les contraintes en n'acceptant QUE des combinaisons de paramètres correspondant à des constructeurs explicites. Ceci garantirait une sécurité très forte par rapport à la situation actuelle.

Un Post renvoie la nouvelle entité dans son état final.

Delete permet de supprimer une entité. Qu'il s'agisse d'une suppression logique ou physique, le fait d'être passé par un Delete implique que l'entité n'est plus accessible par aucun moyen. Son URL directe (avec son Id) renvoie une 404. C'est pour cette raison qu'un Delete ne renvoie pas l'entité, car elle n'existe plus. Il renvoie un 200 OK vide.

Rdd supporte les requêtes multiples. Elles sont atomiques, càd que si une seule plante, aucune modification n'est enregistrée pour aucune requête. Voici les détails pour chaque verbe :

Pour faire des requêtes multiples non atomiques, càd indépendantes les unes des autres, il faut créer une route spécifique, par ex /batchs, qu'on utilise toujours en Post, en envoyant des objets décrivant les requêtes qu'on veut faire, par ex [ { "method": "Put", "href": "/api/users/123", "data": { "address": "newAddress", "phoneNumber": "01 02 03 04 05" } }, ... ]. Cette feature n'est actuellement pas implémentée par Rdd.

Si une route n'est pas décrite explicitement dans le controller web, elle devrait planter en 404.

Controller Applicatif

Les controllers applicatif sont les seuls points d'entrée dans une application Rdd. En général appelé depuis la couche Web, mais également depuis des tests. Ils peuvent également être appelé depuis un script ou une application console. Ils représentent la surface d'exposition publique de l'application.

Si un controller A veut appeler un controller B, alors la méthode exposée sur B devra être internal de sorte qu'elle ne soit pas exposée au monde extérieur.

Si un même repo GitHub comporte plusieurs applications, avec des projets mutualisés, il ne faut pas appeler un controller B d'une appli P2 depuis un controller A d'une appli P1 ! Si P1 et P2 sont 2 applis qui tournent séparément (ont chacune leur projet web), alors P1 ne peut appeler P2 qu'en faisant une requête HTTP, de sorte de ne pas faire tourner une partie de P2 dans P1 !

Un controller applicatif permet de séquencer un ou plusieurs appels vers des collections. Néanmoins, il est important de noter que si un concept émerge dans la couche Application sans exister dans le Domain, ce n'est pas normal. Il convient alors de créer une collection et une entité dédiée à ce concept et ainsi avoir un controller applicatif qui ne fait qu'un seul appel au lieu de 2 ou 3.

Le controller a également comme responsabilité de valider les données provenant de Web avant d'appeler Domain, de sorte que le Domain ne fonctionne que sur des données valides.

Enfin, il décide de persister les données, de sorte que le Domain n'ait jamais à s'en préoccuper.

Si des traitements sont à lancer après la persistance, notamment l'envoi des mails de notif ou encore l'émission d'un événement (EventBroker power !) alors ce sera également au controller applicatif de le gérer.

Collections du Domain

Les Collections du Domain représentent les points d'entrées vers le Domain. Elles permettent de centraliser l'accès aux entités, et également de leur injecter des services en dépendance.

Elles peuvent s'appuyer sur des helpers, notamment pour l'instanciation des entités et/ou leur patching leur d'un Put.

Si on applique la logique exposée plus haut à propos de l'instanciation des entités (via leur constructeur uniquement), et si ce sont les Controllers applicatifs qui lancent la validation des entités, alors la création se résume à vérifier les droits et ajouter l'entité au Repo.

Lors d'un Put, l'entité est remontée par le Repo, donc censée être valide, puis patchée dans la Collection, qui doit alors la valider. Ici aussi, il faut vérifier les droits, c'est fait lors de la remontée de l'entité.

En lecture, la Collection passe la Query (en provenance de la couche Web) au Repo qui va alors générer une requête SQL correspondante. Les droits sont également joués par le Repo (au plus proche des données).

Entités

Le concept d'entité arrive tout droit du DDD, et nous essayons de le respecter au maximum. Pour ce faire, une entité a forcément un Id, ce qui permet de l'identifier de façon unique. Etant donné qu'on est dans un paradigme REST, les entités ont également un nom et une URL en sortie de la couche Web.

Une Entité doit posséder tout ce qui permet son bon fonctionnement, et n'est pilotée que par sa collection. L'entité doit être capable de gérer son état, ainsi que ses modifications (via des méthodes appropriée). Elle doit aussi gérer sa validation et son instanciation.

Elle peut s'appuyer sur des ValueObjects pour mieux organiser son état.

Elle peut également gérer des sous entités notamment s'il s'agit de commandes.

Repo de l'Infra

Les Repos, à l'instar du concept éponyme dans le DDD, sont là pour reconstruire les entités du Domain lors d'un Get, et pour assurer leur persistance en écriture.

En lecture, ils interprètent la Query qui vient du Web pour la traduire en requête SQL. On peut décomposer le Get en étapes :

Un Repo s'appuie sur un IStorageService pour savoir où sont les données et permettre d'en ajouter. Il n'est plus responsable de persister les données. Cette responsabilité a été transférée à la couche Application, qui utilise un IUnitOfWork, en général branché sur le même DbContext que les Repos, pour persister effectivement les données.

Query

La Query est un objet central dans Rdd, car il matérialise sous forme d'objet la requête Web. Il est interprété par les Repos mais passe à travers le controller applicatif et la collection. C'est notamment pour cela qu'il est dans le Domain.

Néanmoins, quand une requête à une collection est faite depuis une autre collection ou un controller applicatif, on sait exactement dans quel cadre on se situe, et donc on n'a pas besoin d'un objet Query :

Au final, on pourrait interdire l'usage de Query depuis le Domain, et avoir 2 façons d'interagir avec des entités :

Poltuu commented 5 years ago

Avant tout, je rajouterai que RDD est un framework destiné à simplifier la création d'api d'une application

Couche Web:

Réponses HTTP:

Je préciserai que RDD supporte le post, le put et le delete en masse, et que, concernant le post et le put, c'est la version en masse qui doit être prioritisée, car c'est celle ou RDD à le plus de valeur à ajouter, notamment en garantissant que les optimisations de perf sur le sujet sont garanties.

Query:

je dirais plutot que Query est censé correspondre au QuerySpecification pattern, au sens ou rien de web n'est censé transpirer de cet objet. Je suis pour rendre impossible la construction de Query depuis les couches domain.