REST : quelle route pour une relation many to many ?

Le problème exposé dans ce sujet a été résolu.

Bonjour bonjour,

Je dispose de deux modèles Job et Skill, liés par une table pivot. Il s’agit donc d’une relation many to many où un job est associé à 0..* skill et réciproquement.

Je crée une API REST avec un framework MVC avec routes.

J’ai besoin d’une fonctionnalité qui permette d’associer un skill déjà existant à un job déjà existant.

Quelle URL de route me conseilleriez-vous de définir s’il vous plaît ? :) :

  • POST /jobs/{job}/attach_skill/{skill}

  • POST /jobs/{job}/attach_skill avec skill dans le corps de la requête

  • POST /jobs/{job}/skills avec skill dans le corps de la requête

  • POST /jobs/{job}/skills/{skill}/attach

Je pencherais pour la quatrième option, qu’en dites-vous s’il vous plaît ? :)

Voilà voilà merci à vous !

Bonne soirée aux agrumes !

+0 -0

PATCH /jobs/{jobId} ou PATCH /skills/{skillId} avec la modification qui va bien.

Avec JSON Patch ça se fait très bien, ça permet de faire la symétrie « naturellement » et ça réutilise un endpoint générique, à moins que tu aies une raison qui te pousse à faire un endpoint spécifique pour ça que tu ne nous ait pas donnée.

(PS : ça part du principe que le job et le skill existent déjà, mais d’après tes propositions 1 et 4, c’est le cas – sinon c’est juste un PUT /skills/{skillId} avec le skill dans le corps de la requête, qui contient les jobsId à rattacher).

Merci beaucoup SpaceFox pour ta réponse !

Penses-tu par ailleurs qu’il soit envisageable d’écrire cette route : POST /jobs_skills/, avec dans le corps les ID job et skill ?

L’idée étant qu’il s’agit d’un endpoint atteint par la POST , donc fatalement censé créer l’objet JobSkill, qui n’est autre qu’une entrée dans la table pivot job_skill.

+0 -0

Tu peux tout faire, mais ta dernière proposition ressemble furieusement à une exposition du modèle de données en tant qu’API. Il vaut mieux que tu réfléchisses à ton API de façon fonctionnelle, comment tu veux qu’elle soit utilisée de la façon la plus pratique possible.

Tu peux tout faire, mais ta dernière proposition ressemble furieusement à une exposition du modèle de données en tant qu’API. Il vaut mieux que tu réfléchisses à ton API de façon fonctionnelle, comment tu veux qu’elle soit utilisée de la façon la plus pratique possible.

SpaceFox

Merci beaucoup @SpaceFox, c’est exactement cette piste de réflexion ("Il vaut mieux que tu réfléchisses à ton API de façon fonctionnelle, comment tu veux qu’elle soit utilisée de la façon la plus pratique possible") qui me manquait pour savoir comment mettre en place mes routes :D

En particulier : je ne connaissais pas la "norme" (format plus exactement) JSONPatch. https://jsonpatch.com/

Bonne journée à toi, je passe le sujet en résolu !

+0 -0

Après réflexion et un début d’écriture, je ne vais pas pouvoir utiliser JsonPatch. Celui-ci prend en entrée un objet JSON à modifier et un objet JSON d’une séquence de modifications à appliquer dessus.

C’est nickel quand on stocke direct en DB l’objet JSON modifié (retourné par l’application des modifications donc), éventuellement après des validations (i.e.: type contrôles de données formulaires) de données au niveau de chacune des modifications de la séquence de modifications, mais, dans mon cas, j’utilise le framework Laravel et ce n’est pas comme ça que ça marche.

En effet, il faut appeler la fonction $job->attach($skill) pour créer l’entrée dans la table pivot. A contrario, ajouter dans un objet JSON théorique job une clé skills avec pour valeur un objet skill et essayer de l’enregistrer dans la table pivot avec Laravel pourrait marcher mais ça sortirait des sentiers battus prévus par la doc de Laravel, il me semble. (J’insiste sur le "il me semble", n’ayant pas encore suffisamment de pratique/connaissances en Laravel).

Néanmoins, je garde l’idée d’avoir un, et un seul, endpoint généraliste, PATCH /jobs/{job_id} (ou PUT /jobs/{job_id} ça dépend et au niveau de la logique dans le contrôleur ça peut revenir au même). Ce qui est d’ailleurs prévu par la doc de Laravel :) .

Bonjour à tous,

Je me permets de rouvrir ce sujet car j’ai du nouveau, de la part d’un développeur américain (https://martinbean.dev/about/), assez connu dans la communauté Laravel au vu de son parcours pro.

Le contexte est toujours celui du topic : à savoir quelles routes je devrais mettre en place pour ajouter le support "L’utilisateur X postule au job Y".

  • Martin Bean m’a conseillé (voire fortement incité) à utiliser une route qui contiendrait dans l’URL : l’ID du job et le mot-clé attach pour l’association utilisateur<->job, et detach pour la désassociation (c’est-à-dire le cas d’usage où l’utilisateur annule sa candidature). Je suppose, sans être rentré dans les détails avec lui, que ce serait quelque chose du style : /user/jobs/<job_id>/attach par exemple. A noter que fournir <user_id> dans l’URL du genre /users/<user_id>/jobs/<job_id>/attach ne me semble personnellement pas utile, puisque la route n’est appelable que par l’utilisateur authentifié : il n’y a donc pas besoin de fournir son ID dans l’URL, ce qui serait moins confortable pour le développeur frontend.

  • De votre côté : vous m’aviez conseillé de ne pas utiliser une telle route (sauf si nécessité, aviez-vous précisé), mais plutôt d’inclure le job dans la fonction de mise à jour de l’utilisateur PUT/PATCH update, en tant que donnée de charge utile de la requête REST.

Il s’avère que Martin Bean est fermement opposé à votre solution (je dirais même "catégoriquement" au lieu de "fermement").

Je souhaiterais avoir votre avis quant à ce changement de situation : dois-je rétropédaler et rétablir mon code (qui correspondait pas mal à la solution affirmée par Martin Bean), ou conserver votre solution s’il vous plaît ?

Je pense qu’il y a plusieurs "solutions"/réponses de possibles en fait, non ?

Je ne connais pas ce monsieur, et je n’ai jamais vu d’API qui utilise la solution dont il parle.

En fait, ça ressemble très fortement à une exposition directement dans l’API du vocabulaire Laravel. Mais on a pas accès a la discussion dans laquelle il exposerait ses arguments. Note que ça peut être intéressant dans le cas très particulier où tu fais ton API avec ce framework et que tu pars du principe que tous les consommateurs vont l’utiliser aussi (ce qui est toujours un postulat douteux pour une API si elle est publique), parce que dans ce cas tu respectes une espèce de standard de la communauté.

Note aussi que l’opération HTTP PATCH est mal connue et peut faire peur en première approche.

Maintenant, c’est ton API, tu fais ce que tu veux avec.

Oui mais j’essaie juste de trouver une good practice comme on dit :D

Du coup vous défendez chacun votre paroisse haha, je ne sais pas quoi choisir x)

Je te fais confiance et à lui aussi, il passe son temps à aider la commu Laravel sur le discord et il a l’air hyper calé (comme toi).

Je vais jouer à pile ou face :B je rigole

Je précise une chose que j’avais totalement zappée.

Dans Laravel, on a un concept qui s’appelle : FormRequest, et qu’on peut définir pour une route.

Si c’est défini, Laravel exécutera dans l’ordre ses méthodes authorize puis rules. authorize (et donc rules) sont exécutées au tout début de l’appel de la route (et donc avant la "vraie" méthode du contrôleur que la route appelle).

Le but est donc de vérifier que l’utilisateur authentifié a les autorisations nécessaires (authorize) (genre par exemple : est-ce que son rôle est bien "applier" pour pouvoir postuler ? Et non pas le rôle "firm" puisqu’une société ne peut pas postuler). Si oui, on vérifie les inputs de la charge utile de la requête du client (rules) (genre si le champ "password" contient bien 13 caractères par exemple). Si oui, alors la "vraie" méthode du contrôleur est exécutée.

Et donc, là où je veux en venir, c’est que : si j’utilise ta solution, @SpaceFox (laquelle est très logique et cohérente, et je l’ai mise en place actuellement) , alors la méthode authorize telle que je peux la définir va forcément reposer sur… la payload de la requête ! Ce qui est un non-sens puisque comme tu l’as certainement compris, la payload est vérifiée dans rules qui s’exécute après authorize : Laravel a dans l’idée de d’abord checker si l’utilisateur a les permissions et ENSUITE checker le contenu de la payload. Le fait de devoir définir authorize en fonction du contenu de la payload est donc une sorte de non-sens.

J’illuse cela :

    public function authorize()
    {
		if(request()->has('job') && request()->has('job.attach_or_detach')) {
			if(request()->input('job.attach_or_detach')) {
				return $this->user()->can('attach-job');
			} else {
				$job = Job::findOrFail(request()->input('job.id'));
				return $this->user()->can('detach-job', $job);
			}
		}

        return true;
    }
  • Comme tu peux le voir : si la payload contient job.attach_or_detach == TRUE (= si l’utilisateur postule), alors je vérifie qu’il est bien un "applier" et non une "firm" par exemple (c’est ce qu’il y a derrière $this->user()->can('attach-job');).

  • Similairement, si ça vaut FALSE (= si l’utilisateur annule sa candidature au job), alors je vérifie qu’il est bien un "applier" et non une "firm" et que le job de la requête fait bien partie des jobs auxquels il a postulés (c’est ce qu’il y a derrière $this->user()->can('detach-job');.

Comme tu peux le voir, authorize se base sur la payload (job.attach_or_detach), alors que Laravel ne vérifiera le bon format etc. de la payload (dans rules) qu’après avoir exécuté authorize :

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, mixed>
     */
    public function rules()
    {
		return [
			'name' => 'nullable|string',

			'job' => 'nullable|array:id,attach_or_detach,message|required_array_keys:id,attach_or_detach',
			'job.id' => 'integer|gt:0',
			'job.attach_or_detach' => 'boolean',
			'job.message' => 'required_if:job.attach_or_detach,true|string',
		];
    }

Je ne sais pas trop où tu peux en venir.

Le triptyque autorisation / validation de la charge utile / application est un classique quel que soit le langage. Si, fonctionnellement, ton API a un contrôle d’accès qui doit être fait sur un élément variable (un ID d’élément par exemple), effectivement cet élément devrait se retrouver dans l’URL si c’est possible. Sinon, il y a toujours possibilité d’avoir un refus d’accès lancé par les couches suivantes.

Mais surtout, conçois ton API avant de la coder. Tu te poses encore des questions structurantes à son sujet mais tu expliques ton problème avec des morceaux de code. Ça n’est pas logique.

Je ne sais pas trop où tu peux en venir.

Le triptyque autorisation / validation de la charge utile / application est un classique quel que soit le langage. Si, fonctionnellement, ton API a un contrôle d’accès qui doit être fait sur un élément variable (un ID d’élément par exemple), effectivement cet élément devrait se retrouver dans l’URL si c’est possible. Sinon, il y a toujours possibilité d’avoir un refus d’accès lancé par les couches suivantes.

Mais surtout, conçois ton API avant de la coder. Tu te poses encore des questions structurantes à son sujet mais tu expliques ton problème avec des morceaux de code. Ça n’est pas logique.

SpaceFox

Ça marche, merci pour ta réponse !

Du coup il faut bien que je passe l’ID du job dans l’URL et une route comme /api/user/jobs/<id du job>/attach semble adéquate. (je préfère ne pas déléguer ces vérifications aux couches suivantes)


Mais surtout, conçois ton API avant de la coder. Tu te poses encore des questions structurantes à son sujet mais tu expliques ton problème avec des morceaux de code. Ça n’est pas logique.

Yes mais c’est parce que je n’ai jamais eu de remarque à ce sujet qui aurait permis de m’améliorer, dans mes deux sociétés pour lesquelles j’ai travaillé. C’est pour ça que je pratique et que j’en viens à poser ce genre de questions. Je cherche un peu les "bonnes pratiques".

(Par contre souvent vous dites que concevoir à l’avance c’est pas optimal pour développer, et qu’il vaut mieux pratiquer et refactored, modifier l’existant pour l’adapter. Du coup c’est à appliquer pour le dev de fonctionnalités client, et pas pour des trucs qui doivent être carrés et structurés correctement dès le début comme une API ?)

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte