Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RESTful way of dealing with bi-directional relationships between resources

Let's suppose I wanted to design a REST api which talks about songs, albums and artists (actually I do, like 1312414 people before me).

A song resource always is associated with the album it is part of. Conversely, the album resource is associated with all songs it contains. The associations are expressed in the resource representations by way of links.

As such, the representations would look something like this:

{
   song: 'xyz',
   links: [
      { rel: 'album', url: '.../albums/abc' }
   ]
}

{
   album: 'abc',
   links: [
      { rel: 'song', url: '.../songs/xyz' },
      { rel: 'song', url: '...' },
      { rel: 'song', url: '...' },
      { rel: 'song', url: '...' }
   ]
}

Given, that I want this to hold true (maybe the problem lies in the "Given"), how then do I design my API, such that the creation of an album or song resource does not have side effects on previously existing song or album resources?

This is some kind of a chicken/egg problem. If I create a song resource first (POST /songs/) and then create an album resource (POST /albums/), the song resource gets modified as part of the album creation (which is bad according to REST principles), because the association between the two resources is being updated on the server. Likewise for the the scenario where I create the album first, the song second.

I guess I could avoid the whole issue by avoiding the side effect on the server and passing the burden of managing the bi-directional relationships to the client.

Also, I don't want album and songs to be created atomically as a whole.

The only thing I can think of right now, is to include the aforementioned side effect in the semantics of my API by responding to a resource creation with a representation which contains a list of links to resources which have been modified as a result of the request. That makes the side effect explicit, but still not restful though.

like image 274
Mark Lehmacher Avatar asked Feb 07 '12 20:02

Mark Lehmacher


1 Answers

Nothing about REST says that manipulation of the state of one resource cannot alter the state of another resource. About the closest REST gets to that is the notion of idempotent actions, which say only that repeating them will result in the same state.

So, in your case, there's nothing inherently incorrect about a Song resource being able to, effectively, add itself to an Album resource. Nor is there anything inherently wrong about an Album resource being able to say that a Song resource is a part of it.

Now, given your business requirements, you might want to either a.) Change how you represent songs/albums or b.) Allow songs/albums to be related in a m/m fashion instead of 1/1. The reason for this is that, depending on how you structure your data and select resources (ie. addressable units) in your system, you are actually modeling different data relationships, and this, I think, is the crux of the issue you are facing.

With Songs and Albums separate resources in your system, enforcing more restrictive relationships, like 1/1 instead of m/m, requires much more work and specification in your content types. You have to handle the cases where two different albums both think they contain a single song, but a 1/1 relationship doesn't allow that.

If you have an Album object explicitly contain or own the Song objects, then there's less of an issue, since an Album can only manipulate it's own songs and not those of any other Album. This changes the data model, however, since Songs are no longer first-class citizens, but instead are underneath the album that owns them.

This is the key point of the whole issue... you must decide who owns the relationship.

There's nothing wrong with both sides (Album and Song) owning it. This works perfectly for a m/m relationship, but for a 1/1 relationship, that requires either a lot more management overhead (ie. something else actually owns the relationship).

If you want a 1/1 relationship without all the overhead, however, you have to have one of the participating entities own the relationship, meaning that there is only one path to change it... through the owning entity.

like image 65
cdeszaq Avatar answered Oct 01 '22 03:10

cdeszaq