Where we should put search action?
In GET /search/:text
. This will return a JSON array containing the matches, every match containing the album it belongs to. This makes sense, because the client may be interested not in the track itself, but the entire album (imagine that you are searching for a song which, you believe, was in the same album as the one you remember the name).
it will be not that good to return parent ids with each so. Am I wrong?
Individual tracks can contain the album. This will ensure that the track representation is uniform if you can get a track either through an album or through search (no album here).
Which is better?
As previously stated, including the album makes sense. While the third point (with the relative URI) can be interesting in some cases (you don't have to think about the way the URI should be formed), it has a drawback of not providing explicitly the album. The fourth point corrects this. If you see the benefit of having the relative URI in the response, you can combine the point 3 and 4.
Or maybe I'm dumb?
Choosing good URIs is not an easy task, especially since there is no single right answer. If you develop the client at the same time as the API, it may help you to visualize better how the API could be used. This being said, other people may then prefer other usages you weren't thinking about when developing the API.
An aspect which may be problematic is how you organize data internally, i.e. the usage of a hierarchy. From your comment, you are wondering what should contain a response to GET /artist/1/album/10/song/3/comment/23
, which shows a very tree-oriented vision. This can lead to a few issues when extending the system later. For instance:
- What if a song doesn't have an album?
- What if an album has several artists?
- What if you want to add a feature which makes it possible to comment albums?
- What if there should be comments of comments?
- etc.
This is essentially the problem I explained in my blog: a tree representation has too many limitations to be effectively used in many cases.
What happens if you destroy the hierarchy? Let's see.
GET /albums/:albumId
returns a JSON containing the meta information about the album (such as the year when it was published or the URI of the JPEG showing the album cover) and an array of tracks. For example:
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"
}
...
]
}
Why do I include, for instance, the length of each track? Because I imagine that the client showing an album may be interested by listing the tracks by title, but also show the length of each track—most clients do. On the other hand, I may not show the composer(s) or the artist(s) for every track, because I decide that this information is not necessary at this level. Obviously, your choices may be different.
GET /tracks/:trackId
returns the information about a specific track. Since there is no hierarchy any longer, you don't need to guess the album or the artist: the only thing you really have to know is the identifier of the track itself.
Or maybe even not? What if you can specify it by name with 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
}
}
Now look closer at albums
; what do you see? Right, not one, but two albums. If you have an hierarchy, you can't do that (unless you duplicate the record).
GET /comments/:objectGid
. You may have spotted the ugly GUIDs in the responses. Those GUIDs make it possible to identify the entity across the database in order to perform tasks which can be applied to albums, or artists, or tracks. Such as commenting.
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"
}
]
The comment references the concerned object, making it possible to go to it when accessing the comment outside its context (for instance when moderating the latest comments through GET /comments/latest
).
Note that this doesn't mean that you should avoid any form of hierarchy in your API. There are cases where it makes sense. As a rule of thumb:
If the resource makes no sense outside the context of its parent resource, use hierarchy.
If the resource can live (1) alone or (2) in a context of parent resources of different types or (3) have multiple parents, the hierarchy should not be used.
For instance, lines of a file make no sense outside the context of a file, so:
GET /file/:fileId
and:
GET /file/:fileId/line/:lineIndex
are fine.