D'accord, nous avons deux ressources: Album
et Song
. Voici l'API:
GET,POST /albums
GET,POST /albums/:albumId
GET,POST /albums/:albumId/songs
GET,POST /albums/:albumId/songs/:songId
Nous savons que nous détestons une chanson, elle s'appelle Susy
, par exemple. Où devrions-nous mettre l'action search
?
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.
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
Hybride. C'est maintenant très pratique car nous avons besoin de l'identifiant de l'album pour effectuer une requête OPTIONS
pour obtenir des métadonnées.
List,Create /album/:albumId/songs
Update,Delete /songs/:songId
POST /songs/search # also, solution for first question
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.
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!
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, il est logique d'inclure l'album. 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. D'après votre commentaire, vous vous demandez ce qui 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:
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.
GET /albums/:albumId
renvoie 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 durée de chaque piste - la plupart des clients le font. Par contre, 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.
GET /tracks/:trackId
renvoie 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 avec 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
}
}
Regardons maintenant 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).
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. Comme commenter.
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 via 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 parents de types différents 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.