URL REST imbriquées et identifiant parent, quelle est la meilleure conception?

20

D'accord, nous avons deux ressources: Albumet Song. Voici l'API:

GET,POST /albums
GET,POST /albums/:albumId
GET,POST /albums/:albumId/songs
GET,POST /albums/:albumId/songs/:songId

On sait qu'on déteste une chanson, ça s'appelle Susypar exemple. Où devrions-nous searchagir?

Une autre question. D'accord, maintenant c'est un plus réel. Nous ouvrons l'album 1 et chargeons toutes les chansons. Nous créons des objets JS, chacun contient des données de morceau et a peu de méthodes comme ceci: remove, update.

L'objet Song a un ID, un nom et des trucs, mais n'a aucun indice sur le parent auquel il appartient, car nous récupérons la liste des chansons par requête et il ne sera pas bon de renvoyer les identifiants des parents avec chacun. Ai-je tort?

Donc, je vois peu de solutions, mais je ne suis pas vraiment sûr.

  1. Rendre l'ID parent facultatif - en tant que paramètre get. Cette approche que j'utilise actuellement, mais je pense qu'elle est laide.

    List,Create /songs?album=albumId
    Update,Delete /songs/:songId
    Get /songs/?name=susy # also, solution for first question
    
  2. Hybride. C'est maintenant très pratique car nous avons besoin de l'identifiant de l'album pour effectuer une OPTIONSrequête pour obtenir des métadonnées.

    List,Create /album/:albumId/songs
    Update,Delete /songs/:songId
    POST /songs/search # also, solution for first question
    
  3. Renvoie l'URL complète avec chaque instance de ressource. L'API est la même, mais nous aurons des chansons comme celle-ci:

    id: 5
    name: 'Elegy'
    url: /albums/2/songs/5
    

    J'ai entendu dire que cette approche s'appelait HATEOAS.

  4. Alors ... Pour fournir l'ID parent

    id: 5
    name: 'Elegy'
    albumId: 2
    

Ce qui est mieux? Ou peut-être que je suis stupide? Jetez quelques conseils, les gars!

dt0xff
la source

Réponses:

31

Où devrions-nous placer l'action de recherche?

Dans GET /search/:text. Cela renverra un tableau JSON contenant les correspondances, chaque correspondance contenant l'album auquel il appartient. Cela a du sens, car le client peut être intéressé non pas par le morceau lui-même, mais par l'ensemble de l'album (imaginez que vous recherchez une chanson qui, selon vous, se trouvait dans le même album que celui dont vous vous souvenez du nom).

ce ne sera pas si bon de renvoyer des identifiants parents avec chacun. Ai-je tort?

Les pistes individuelles peuvent contenir l'album. Cela garantira que la représentation des pistes est uniforme si vous pouvez obtenir une piste soit par le biais d'un album, soit par la recherche (pas d'album ici).

Ce qui est mieux?

Comme indiqué précédemment, y compris l'album est logique. Alors que le troisième point (avec l'URI relatif) peut être intéressant dans certains cas (vous n'avez pas à penser à la façon dont l'URI doit être formé), il présente l'inconvénient de ne pas fournir explicitement l'album. Le quatrième point corrige cela. Si vous voyez l'avantage d'avoir l'URI relatif dans la réponse, vous pouvez combiner les points 3 et 4.

Ou peut-être que je suis stupide?

Choisir de bons URI n'est pas une tâche facile, d'autant plus qu'il n'y a pas de bonne réponse unique. Si vous développez le client en même temps que l'API, cela peut vous aider à mieux visualiser comment l'API pourrait être utilisée. Cela étant dit, d'autres personnes peuvent alors préférer d'autres utilisations auxquelles vous ne pensiez pas lors du développement de l'API.

Un aspect qui peut être problématique est la façon dont vous organisez les données en interne, c'est-à-dire l'utilisation d'une hiérarchie. À partir de votre commentaire, vous vous demandez à quoi devrait contenir une réponse GET /artist/1/album/10/song/3/comment/23, qui montre une vision très arborescente. Cela peut entraîner quelques problèmes lors de l'extension ultérieure du système. Par exemple:

  • Et si une chanson n'a pas d'album?
  • Et si un album avait plusieurs artistes?
  • Que faire si vous souhaitez ajouter une fonctionnalité qui permet de commenter des albums?
  • Et s'il devait y avoir des commentaires de commentaires?
  • etc.

C'est essentiellement le problème que j'ai expliqué dans mon blog : une représentation arborescente a trop de limitations pour être utilisée efficacement dans de nombreux cas.

Que se passe-t-il si vous détruisez la hiérarchie? Voyons voir.

  1. GET /albums/:albumIdrenvoie un JSON contenant les méta-informations sur l'album (comme l'année où il a été publié ou l'URI du JPEG montrant la couverture de l'album) et un tableau de pistes. Par exemple:

    GET /albums/151
    {
        "id": 151,
        "gid": "dbd3cec7-b927-423f-894b-742c4c7b54ce",
        "name": "Yellow Submarine",
        "year": 1969,
        "genre": "Psychedelic rock",
        "artists": ["John Lennon", "Paul McCartney", ...],
        "tracks": [
            {
                "id": 90224,
                "title": "Yellow Submarine",
                "length": "2:40"
            },
            {
                "id": 83192,
                "title": "Only a Northern Song",
                "length": "3:24"
            }
            ...
        ]
    }

    Pourquoi dois-je inclure, par exemple, la longueur de chaque piste? Parce que j'imagine que le client montrant un album peut être intéressé en listant les pistes par titre, mais aussi montrer la longueur de chaque piste - la plupart des clients le font. En revanche, je ne peux pas montrer le (s) compositeur (s) ou l'artiste (s) pour chaque morceau, car je décide que cette information n'est pas nécessaire à ce niveau. De toute évidence, vos choix peuvent être différents.

  2. GET /tracks/:trackIdrenvoie les informations sur une piste spécifique. Puisqu'il n'y a plus de hiérarchie, vous n'avez pas besoin de deviner l'album ou l'artiste: la seule chose que vous devez vraiment savoir est l'identifiant du morceau lui-même.

    Ou peut-être même pas? Et si vous pouvez le spécifier par son nom GET /tracks/:trackName?

    GET /tracks/Only%20a%20Northern%20Song
    {
        "id": 83192,
        "gid": "8d9c4311-9d7b-40a4-8aeb-4fe96247fe2b",
        "title": "Only a Northern Song",
        "writers": ["George Harrison"],
        "artists": ["John Lennon", "Paul McCartney", "Ringo Starr"],
        "length": "3:24",
        "record-date": 1967,
        "albums": [151, 164],
        "soundtrack": {
            "uri": "http://audio.example.com/tracks/static/83192.mp3",
            "alias": "Beatles - Only a Northern Song.mp3",
            "length-bytes": 3524667,
            "allow-streaming": true,
            "allow-download": false
        }
    }

    Maintenant, regardez de plus près albums; que vois-tu? Bon, pas un, mais deux albums. Si vous avez une hiérarchie, vous ne pouvez pas le faire (sauf si vous dupliquez l'enregistrement).

  3. GET /comments/:objectGid. Vous avez peut-être repéré les GUID laids dans les réponses. Ces GUID permettent d'identifier l'entité à travers la base de données afin d'effectuer des tâches qui peuvent être appliquées à des albums, des artistes ou des pistes. Telles que des commentaires.

    GET /comments/8d9c4311-9d7b-40a4-8aeb-4fe96247fe2b
    [
        {
            "author": {
                "id": 509931,
                "display-name": "Arseni Mourzenko"
            },
            "text": "What a great song! (And I'm proud of the usefulness of my comment)",
            "concerned-object": "/tracks/83192"
        }
    ]

    Le commentaire fait référence à l'objet concerné, permettant d'y accéder lors de l'accès au commentaire en dehors de son contexte (par exemple lors de la modération des derniers commentaires GET /comments/latest).

Notez que cela ne signifie pas que vous devez éviter toute forme de hiérarchie dans votre API. Il y a des cas où cela a du sens. En règle générale:

  • Si la ressource n'a aucun sens en dehors du contexte de sa ressource parent, utilisez la hiérarchie.

  • Si la ressource peut vivre (1) seule ou (2) dans un contexte de ressources parent de différents types ou (3) avoir plusieurs parents, la hiérarchie ne doit pas être utilisée.

Par exemple, les lignes d'un fichier n'ont aucun sens en dehors du contexte d'un fichier, donc:

GET /file/:fileId

et:

GET /file/:fileId/line/:lineIndex

vont bien.

Arseni Mourzenko
la source
Oui, à partir de la recherche, je peux aussi retourner des informations complètes sur l'album, cela fera une autre ressource - SongSearchResult, c'est bien, je suppose. Mais qu'en est-il des URL? Dois-je fournir parentIDchaque objet et l'utiliser comme paramètre GET ou comme partie normale de l'url? Et si j'ai une profondeur> 2? /artist/1/album/10/song/3/comment/23- il est insensé de fournir chaque identifiant d'artiste, d'album et de chanson dans un commentobjet, mais j'ai entendu dire que c'était un chemin à parcourir, mais n'est-ce pas dégoûtant?!
dt0xff
@ dt0xff: J'ai modifié ma réponse. Je pense que cela devrait maintenant vous donner une image claire de la façon dont la profondeur peut être évitée.
Arseni Mourzenko
Oui, il est clair maintenant qu'il est beaucoup plus facile d'implémenter un point d'entrée pour chaque ressource (sauf une ligne similaire ou autre chose fonctionnelle) sans l'ajouter au parent par url. Merci, vous m'avez convaincu que mon choix était bon et que "l'approche commune" (vraiment, beaucoup de choses imbriquées .. restangularest construite dessus) n'est pas si bonne.
dt0xff
Très bonne réponse. J'ai cependant quelques objections. "Et si un album avait plusieurs artistes?" Différents URI peuvent identifier la même ressource car la relation binaire URI -> ressource est unique à droite (plusieurs à un). Ainsi, les URI /artists/foo/albums/quxet /artists/bar/albums/quxpeuvent parfaitement identifier la même ressource d'album. En d'autres termes, le composant de chemin dans un URI représente une hiérarchie de graphe , pas nécessairement une hiérarchie d' arbre , ce qui le rend approprié pour représenter non seulement des catégories mais aussi des balises.
Maggyero
1
… "C'est essentiellement le problème que j'ai expliqué dans mon blog : une représentation arborescente a trop de limitations pour être utilisée efficacement dans de nombreux cas." Donc non, ce n'est pas un problème. "Et si vous voulez ajouter une fonctionnalité qui permet de commenter des albums?" Ce n'est pas un problème non plus : /artists/foo/albums/qux/comments/7. "Et s'il devait y avoir des commentaires de commentaires?" De même: /artists/foo/albums/qux/song/5/comments/2/comments/8.
Maggyero