Wednesday, February 13, 2008

REST-based service design, v2

I want to revisit the basic "favorite food" service from last post, in light of some further discussion I've had with colleagues.

http://host/path/{userid}/favorites
  • GET: returns a list of the user's favorite foods, in some representation. Returns 404 NOT FOUND if the user has never specified favorites.
  • PUT: sets the list of the user's favorite foods to be the value sent, creating it if necessary.
  • DELETE: removes the user's favorite list. This is different than doing a PUT with an empty list.
  • POST: returns 405 METHOD NOT ALLOWED.
http://host/path/{userid}/favorites/{food}
  • GET: returns 200 OK if the food is a favorite of the user. Returns 404 NOT FOUND if the food is not a favorite of the user.
  • PUT: makes the food one of the user's favorites
  • DELETE: makes the food *not* a favorite
  • POST: returns 405 METHOD NOT ALLOWED

So, PUT can create a resource if it doesn't exist, and DELETE of a URI means the next GET of it (in the absence of any other operations) will be a 404 NOT FOUND.

One note is that as a client of this API, I want and can make use of the most atomic operation available. For example, to make "spaghetti" a favorite food, I could either:

  1. GET http://host/path/{userid}/favorites
  2. PUT http://host/path/{userid}/favorites (new list with spaghetti added in)

or I could just:

  1. PUT http://host/path/{userid}/favorites/spaghetti

Note that in the first case, I might have an atomicity issue in the presence of concurrent access, so I might need to build in some sort of optimistic locking protocol, where the representation of the favorites list has a version number on it that is checked by the PUT operation. However, if I just use the second method, I don't have this issue, because the server handles all my concurrency/transactional stuff for me.

2 comments:

schapht said...

One thing that "smells" to me about this is that you've created a data service with three operations. Read, Update and Delete. Where Update is an implied Create. Since CRUD is such a common acronym I can't help but think that there's some reason not to cause implicit data creation. I can't say I know what that reason is, but it's probably worth it to investigate.

Jon Moore said...

Yes, I can see where you're coming from. In this case, in the way I'm thinking about it, there isn't any data to update -- the only data we care about is whether the resource exists or not (i.e. the boolean flag of whether a food is a favorite or not). So I think this is just a degenerate case. Maybe the right way to look at this is that we've created a resource with Create, Read, and Delete, but no Update. So it's CRD. That might make it smell a little less?