Conception d'API REST pour les pages Web avec des assistants

11

J'ai une page Web au format assistant. Le bouton de soumission à l'API sera à la 4ème étape de l'assistant. Cependant, je souhaite que les données saisies soient stockées dans la base de données avant de passer à l'étape suivante de l'assistant. Je veux également que l'API REST fonctionne pour les pages ayant un seul onglet.

J'ai donc conçu l'API pour prendre une action de paramètre de requête = brouillon ou soumettre. Si l'action est provisoire, seuls certains champs sont obligatoires. Si une action est soumise, tous les champs sont obligatoires. Les validations dans la couche de service de l'API REST seront effectuées en fonction du paramètre de requête. On dirait que je dois spécifier explicitement les clauses if / else dans la documentation. Est-ce une forme acceptable de conception RESTful? Quelle serait la meilleure conception avec ces exigences?

TechCrunch
la source
3
Pourquoi les données intermédiaires doivent-elles être stockées dans la base de données?
Dan1701
2
@ Dan1701: pour reprendre l'assistant depuis une autre machine. Lors du remplissage de formulaires longs et complexes, il peut prendre quelques jours pour compléter toutes les données requises, car l'utilisateur peut ne pas avoir toutes les données nécessaires en main, ou l'utilisateur peut avoir besoin de rassembler des fichiers supplémentaires à télécharger à partir de différents endroits. Si vous pouvez reprendre à partir d'un autre appareil, vous pouvez charger l'assistant pour télécharger une photo depuis un téléphone mobile, et continuer à taper une longue description / argument avec un vrai clavier sur le bureau, etc.
Lie Ryan
Dans ce cas, je pense que la réponse de @ guillaume31 est logique.
Dan1701

Réponses:

7

Puisque vous souhaitez conserver des éléments sur le serveur entre les étapes de l'assistant, il semble parfaitement acceptable de considérer chaque étape comme une ressource distincte. Quelque chose dans ce sens:

POST /wizard/123/step1
POST /wizard/123/step2
POST /wizard/123/step3

En incluant des liens hypermédias dans la réponse, vous pouvez informer le client de ce qu'il peut faire après cette étape - avancer ou reculer pour les étapes intermédiaires, et rien pour l'étape finale. Vous pouvez en voir un exemple dans la figure 5 ici .

guillaume31
la source
J'utilise Angular pour l'interface utilisateur. Je ne sais donc pas à quel point la machine d'état est utile. Mais je pense que la ressource basée sur les étapes semble être plus significative que la gestion d'une autre table. De plus, je devrais pouvoir tout soumettre en une seule étape. Je vais essayer aujourd'hui ce design. Merci pour l'aide.
TechCrunch
Je vous en prie. Soit dit en passant, l'approche «à deux tables» ne s'exclut pas mutuellement. Avoir une ressource HTTP par étape ne dicte pas votre modèle d'objet sur le serveur d'applications, encore moins le schéma de base de données. Ce n'est qu'une représentation Web.
guillaume31
1
@TechCrunch Fondamentalement, Guillaume signifie que l'objet / la table représentant le formulaire peut être divisé en parties, où une partie du modèle est enregistrée à chaque étape. En fait, vous pouvez simplement faire en sorte que chaque "étape" soit un formulaire pour une partie du modèle entier . Et si vous adoptez cette approche, cela rend l'architecture incroyablement simple. Chaque POST sur le serveur (créera ou) mettra à jour le même modèle, et chaque GET chargera le même modèle, et chaque étape sera un formulaire pour remplir des ensembles de champs qui sont sémantiquement significatifs (appartiennent ensemble). Et simplement avoir un booléen sur le modèle pour in_progressou draft.
Chris Cirefice
3

J'avais dû faire quelque chose de similaire il y a quelque temps, et ce qui suit décrit ce que nous nous retrouvons.

Nous avons deux tables, Item et UnfinishedItem. Lorsque l'utilisateur remplit les données avec l'assistant, les données sont stockées dans la table UnfinishedItem. A chaque étape de l'assistant, le serveur valide les données saisies lors de cette étape. Lorsque l'utilisateur a terminé avec l'Assistant, l'Assistant affiche un formulaire masqué / en lecture seule dans une page de confirmation qui affiche toutes les données à soumettre. L'utilisateur peut consulter cette page et revenir à l'étape appropriée pour corriger les erreurs. Une fois que l'utilisateur est satisfait de ses entrées, l'utilisateur clique sur Soumettre et l'assistant soumet ensuite toutes les données des champs de formulaire masqué / en lecture seule au serveur API. Lorsque le serveur API traite cette demande, il réexécute toutes les validations qu'il a effectuées à chaque étape de l'assistant et effectue des validations supplémentaires qui ne correspondent pas aux étapes individuelles (par exemple, les validations globales, les validations coûteuses).

Les avantages de l'approche à deux tables:

  • dans la base de données, vous pouvez avoir des contraintes plus strictes sur la table Item que sur la table UnfinishedItem; vous n'avez pas besoin d'avoir des colonnes facultatives qui seront réellement requises lorsque l'assistant aura terminé.

  • Les requêtes agrégées sur les éléments finis pour les rapports sont plus faciles car vous n'avez pas à vous rappeler d'exclure les éléments inachevés. Dans notre cas, nous n'avons jamais eu besoin de faire des requêtes agrégées entre Item et UnfinishedItems, donc ce n'est pas un problème.

Le désavantage:

  • Il est sujet à la duplication de la logique de validation. Le framework web que nous avons utilisé, Django, rend cela un peu plus supportable car nous avons utilisé l'héritage de modèle avec un peu de méta-magie pour changer les contraintes dont nous devons être différents dans Item et UnfinishedItem. Django génère la majeure partie de la validation de la base de données et des formulaires à partir du modèle, et nous n'avons qu'à pirater quelques validations supplémentaires par-dessus.

J'ai envisagé d'autres possibilités et pourquoi nous ne les avons pas choisies:

  • enregistrer les données dans des cookies ou un stockage local: l'utilisateur ne peut pas continuer sa soumission à partir d'un autre appareil ou s'il supprime l'historique de son navigateur
  • stocker l'UnfinishedItem en tant que données non structurées (par exemple JSON) sur la base de données ou le magasin de données secondaire: je vais devoir définir une logique d'analyse et je ne peux pas utiliser la validation automatique de modèle / formulaire de Django.
  • faire la validation par étape côté client: je vais devoir dupliquer la logique de validation entre Python / Django et JavaScript.
Lie Ryan
la source
1
+1 pour signaler les validations sur les modèles de type «brouillon» et les modèles «finis»; Je n'y ai pas pensé, et c'est un point important à prendre en considération. Sinon, vous auriez probablement un tas de ifdéclarations vérifiant l'état du brouillon tout au long de vos validations, ce qui ne serait tout simplement pas bon. Bien que certains cadres très sophistiqués comme Ruby on Rails puissent considérablement simplifier ce problème s'ils sont correctement mis en œuvre.
Chris Cirefice
1

J'ai implémenté cela d'une manière similaire à un mélange de solutions @ guillauma31 et @Lie Ryan.

Voici les concepts clés:

  1. Il existe un assistant en 3 étapes qui peut être partiellement conservé jusqu'à ce qu'il soit terminé.
  2. Chaque étape a sa propre ressource (par exemple .: /users/:id_user/profile/step_1, .../step_2, etc.)
  3. À chaque étape, les données et l'état d'achèvement peuvent être récupérés via les requêtes GET et persistés via les requêtes PATCH.
  4. Chaque ressource a ses propres règles de validation pour les données saisies.
  5. Chaque étape renvoie une clé qui doit être utilisée dans l'entrée de l'étape suivante pour garantir la séquence. Une fois utilisé ou généré, ce jeton expire.
  6. À la dernière étape, nous avons toutes les données nécessaires dans la base de données et un écran de confirmation s'affiche. Cette confirmation appelle une autre ressource pour marquer les données comme complètes (par exemple:) .../profile/confirm. Cette ressource n'a pas besoin de recevoir à nouveau toutes les données. Il marque uniquement les données comme correctes et complètes.
  7. Il existe une routine planifiée qui efface ces entrées incomplètes qui ont plus de quelques jours.

Les gars du front-end doivent prendre soin des jetons pour que le flux de va-et-vient de l'assistant fonctionne.

L'API est sans état et atomique.

Pour faire fonctionner un "assistant en une étape" avec cette configuration, vous devez modifier certaines choses, comme supprimer le flux de jetons ou créer une ressource pour renvoyer des jetons en fonction du type d'assistant ou même créer une nouvelle ressource uniquement pour remplir ce single spécifique assistant étape (comme PUT /users/:id_user/profile/).

Ricardo Souza
la source