13

I have three questions about REST API design that I'm hoping someone can shed some light on. I've searched relentlessly for many hours but haven't found answers to my questions anywhere (maybe I just don't know what to search for?).

Question 1

My first question has to do with actions/RPC. I've been developing a REST API for a while and I'm used to thinking of things in terms of collections and resources. However, I've come across a couple cases where the paradigm doesn't seem to apply and I'm wondering if there is a way to reconcile this with the REST paradigm.

Specifically, I have a case where modifying a resource causes an email to be generated. However, at a later point the user can specifically indicate that they want to resend the email that was sent earlier. When resending the email no resource is modified. No state is changed. It's simply an action that needs to occur. The action is tied to the specific resource type.

Is it appropriate to mix some sort of action call with a resource URI (e.g. /collection/123?action=resendEmail)? Would it be better to specify the action and pass the resource id to it (e.g. /collection/resendEmail?id=123)? Is this the wrong way to be going about it? Traditionally (at least with HTTP) the action being performed is the request method (GET, POST, PUT, DELETE), but those don't really allow for custom actions with a resource.

Question 2

I use the querystring portion of the URL to filter the set of resources returned when querying a collection (e.g. /collection?someField=someval). Within my API controller I then determine what kind of comparison it is going to do with that field and value. I've found this really doesn't work. I need a way to allow the API user to specify the type of comparison they want to perform.

The best idea I've come up with so far is to allow the API user to specify it as an appendage to the field name (e.g. /collection?someField:gte=someval - to indicate that it should return resources where someField is greater than or equal to (>=) whatever someval is. Is this a good idea? A bad idea? If so, why? Is there a better way to allow the user to specify the type of comparison to perform with the given field and value?

Question 3

I often see URI's that look something like /person/123/dogs to get the persons dogs. I generally have avoided something like that because in the end I figure that by creating a URI like that you are actually just accessing a dogs collection filtered by a specific person ID. It would be equivalent to /dogs?person=123. Is there ever really a good reason for a REST URI to be more than two levels deep (/collection/resource_id)?

4
  • 10
    You have three questions. Why not post them separately? Jul 11, 2013 at 10:56
  • 3
    It would be better to break this out into 3 separate questions. A viewer may be able to an excellent answer to one but not all of the questions.
    – user53019
    Jul 11, 2013 at 11:25
  • 2
    I think they are all related. The title is a little high-level but this question will help many people and is easily found during a SE search. This question should become Community Wiki once enough votes and substance has been added. It took me weeks to research this stuff. Jul 11, 2013 at 12:54
  • 1
    It may have been better to post them separately, IDK. However, as @AndrewFinnell mentioned, I thought it'd be a good idea to keep the questions together since these have been the toughest REST related questions I've had and it'd be nice for other people to be able to find the answers together. Jul 11, 2013 at 14:29

5 Answers 5

13

Is it appropriate to mix some sort of action call with a resource URI (e.g. /collection/123?action=resendEmail)? Would it be better to specify the action and pass the resource id to it (e.g. /collection/resendEmail?id=123)? Is this the wrong way to be going about it? Traditionally (at least with HTTP) the action being performed is the request method (GET, POST, PUT, DELETE), but those don't really allow for custom actions with a resource.

I'd rather model that in a different way, with a collection of resources representing the emails that are to be sent; the sending will be processed by the internals of the service in due course, at which point the corresponding resource will be removed. (Or the user could DELETE the resource early, causing a canceling of the request to do the send.)

Whatever you do, don't put verbs in the resource name! That's the noun (and the query part is the set of adjectives). Nouning verbs weirds REST!

I use the querystring portion of the URL to filter the set of resources returned when querying a collection (e.g. /collection?someField=someval). Within my API controller I then determine what kind of comparison it is going to do with that field and value. I've found this really doesn't work. I need a way to allow the API user to specify the type of comparison they want to perform.

The best idea I've come up with so far is to allow the API user to specify it as an appendage to the field name (e.g. /collection?someField:gte=someval - to indicate that it should return resources where someField is greater than or equal to (>=) whatever someval is. Is this a good idea? A bad idea? If so, why? Is there a better way to allow the user to specify the type of comparison to perform with the given field and value?

I'd rather specify a general filter clause and have that as an optional query parameter on any request to fetch the contents of the collection. The client can then specify exactly how to restrict the set returned, in whatever way you desire. I'd also worry a bit about the discoverability of the filter/query language; the richer you make it, the harder it is for arbitrary clients to discover. An alternative approach which, at least theoretically, deals with that discoverability issue is to allow making restriction sub-resources of the collection, which clients obtain by POSTing a document describing the restriction to the collection resource. It's still a slight abuse, but at least it's one you can clearly make discoverable!

This sort of discoverability is one of the things that I find least strong with REST.

I often see URI's that look something like /person/123/dogs to get the persons dogs. I generally have avoided something like that because in the end I figure that by creating a URI like that you are actually just accessing a dogs collection filtered by a specific person ID. It would be equivalent to /dogs?person=123. Is there ever really a good reason for a REST URI to be more than two levels deep (/collection/resource_id)?

When the nested collection is truly a sub-feature of the outer collection's member entities, it is reasonable to structure them as a sub-resource. By “sub-feature” I mean something like UML composition relation, where destroying the outer resource naturally means destroying the inner collection.

Other types of collection can be modeled as an HTTP redirect; thus /person/123/dogs can indeed be responded to by doing a 307 that redirects to /dogs?person=123. In this case, the collection isn't actually UML composition, but rather UML aggregation. The difference matters; it is significant!

1
  • 2
    You have solid points overall. However, while the resendEmail action could be handled by creating a collection and POSTing to it, that seems less natural. I don't, in fact, store anything in the database when an email is resent (no need). No resource is modified, thus it is simply an action that either succeeds or fails. I couldn't return a resource ID that exists beyond the life of the call, making such an implementation a hack instead of being RESTful. It's simply not a CRUD operation. Jul 11, 2013 at 14:20
5

It is understandable to be a little confused about how to properly use REST based on all the ways I have seen large companies design their REST APIs.

You are correct in that REST is a Resource Collection system. It stands for Representational State Transfer. Not a great definition if you ask me. But the main concepts are the 4 HTTP VERBs and being stateless.

The important piece to note is that you only have 4 VERBS with REST. These are GET, POST, PUT and DELETE. Your resend example would be adding a new Verb to REST. This should be a red flag.

Question 1

It is important to realize that the caller of your REST API should not have to know that performing a PUT on your collection would result in an e-mail being generated. That smells of a leak to me. What they could know is that performing a PUT could result in extra tasks which they could query later. They would know this by performing a GET on the recently created resource. That GET would return the resource and all of the Task resource id's associated with it. You can then later query those tasks to determine their status and even submit a new Task.

You have a few options.

REST - Task resource based approach

Create a tasks resource in which you can submit specific tasks into your system to perform actions. You can then GET the task based on the ID it returned to determine it's status.

Or you can mix in a SOAP over HTTP Web Service in order to add some RPC to your architecture.

querying for all tasks for a specific resource

GET http://server/api/myCollection/123/tasks

{ "tasks" :
    [ { "22333" : "http://server/api/tasks/223333" } ] 
}

task resource example

PUT http://server/api/tasks

{ 
    "type" : "send-email" , 
    "parameters" : 
    { 
         "collection-type" : "foo" , 
         "collection-id" : "123" 
    } 
}

==> returns id of task

223334

GET http://server/api/tasks/223334

{ 
    "status" : "complete" , 
    "date" : "whenever" 
}

REST- Using POST to trigger actions

You can always POST additional data to a resource. In my opinion this would violate the spirit of REST but it would still be compliant.

You can do a POST similar to this:

POST http://server/api/collection/123

{ "action" : "send-email" }

You will be updating the resource 123 from collection with additional data. That additional data is essentially a action telling the backend to send an email for that resource.

The issue I have with this is that a GET on the resource will return this updated data. However, this would solve your requirements and still be RESTful.

SOAP - Web Service that accepts resources obtained from REST

Create a new WebService in which you can send e-mails based on the previous resource ID from the REST API. I won't go into detail about SOAP here as the original question is about REST and these two concepts/technologies should not be compared as it's Apples and Oranges.

Question 2

You also have a few options here:

It appears many larger companies that publish REST API's expose a search collection that is really just a way to pass in query parameters to return resources.

GET http://server/api/search?q="type = myCollection & someField >= someval"

Which would return a collection of fully qualified REST resources such as:

{
    "results" : 
       { [ 
             "location" : "http://server/api/myCollection/1",
             "location" : "http://server/api/myCollection/9",
             "location" : "http://server/api/myCollection/56"
         ]
       }
}

Or you can allow something like MVEL as a query parameter.

Question 3

I prefer the sub-levels than having to go back up and query the other resource with a query parameter. I do not believe there is any rule one way or another. You can implement both ways and allow the caller to decide which is more appropriate based on how they first entered into the system.

Notes

I disagree about the readability comments from others. Despite what some might think REST is still not for human consumption. It is for machine consumption. If I want to see my Tweets I use Twitters regular website. I do not perform a REST GET with their API. If I want to programmatically do something with my tweets then I use their REST API. Yes APIs should be understandable, but your gte isn't that bad, it's just not intuitive.

The other main thing with REST is that you should be able to start at any give point in your API and navigate to all other associated resources WITHOUT know the exact URL of the other resources ahead of time. The results of the GET VERB in REST should return the full REST URL of the resources it references. So instead of a query returning the ID of a Person object, it would return the Fully Qualified URL such as http://server/api/people/13. Then you can always programmatically navigate the results even if the URL changed.

Response to comment

In the real world there are in fact things that need to happen that don't Create, Read, Update or Delete (CRUD) a resource.

Additional actions can be taken on resources. Typical relational databases support the concept of Stored Procedures. These are additional commands that can be executed on a set of data. REST does not inherently have that concept. And there is no reason it should. These types of actions are perfect for RPC or SOAP Web Services.

This is the general problem I see with REST APIs. Developers don't like the conceptual limitations that surround REST so they adapt it to do whatever they would like. That breaks it from being a RESTful service though. Essentially those URL's become GET calls on pseudo-REST-like servlets.

You have a few options:

  • Create a task resource
  • Support POSTing additional data to the resource to perform an action.
  • Add the additional commands through a SOAP Web Service.

If you used a query parameter which HTTP VERB would you use to resend the email?

  • GET - Does this resend the email AND return the resource's data? What if a system cached that URL and treated it like the unique URL for that resource. Every time they hit the URL it would resend an email.
  • POST - You didn't actually send any new data to the resource, just an additional query parameter.

Based on all the given requirements, doing a POST on the resource with an action field as POST data will solve the problem.

3
  • 3
    While REST implemented via HTTP gives you those 4 verbs I'm not convinced that those verbs should be the end of it. In the real world there are in fact things that need to happen that don't Create, Read, Update or Delete (CRUD) a resource. Resending an email is one of those things. I don't need to store or modify anything in the database. It's simply an action that either succeeds or fails. Jul 11, 2013 at 14:24
  • @JustinWarkentin I understand what your needs are. But that doesn't make REST something it is not. Adding a new verb to the URL is against the REST architecture. I will update my answer to offer another alternative that would be RESTful. Jul 11, 2013 at 18:08
  • @JustinWarkentin Check out 'REST - Using POST to trigger actions' in my answer. Jul 11, 2013 at 18:27
0

Question 1: Is it appropriate to mix some sort of action call with a resource URI [or] would it be better to specify the action and pass the resource id to it?

Good question. In this case I'd advise you use the latter approach, namely specify the action and pass a resource ID to it. This way, when your resource is first modified, it in turn calls the /sendEmail action (side note: no need to call it "resend") as a separate RESTful request (which you can later call again and again, independently of the resource being modified).

Question 2: regarding using a comparison operator like so: /collection?someField:gte=someval

Whilst this is technically ok, it is probably a bad idea. One of the key principles of REST is readability. I'd suggest you simply pass the comparison operator as another parameter, for example: /collection?someField=someval&operator=gte and of course design your API so that it caters for a default case (in the event that the operator parameter is left out of the URI).

Question 3: Is there ever really a good reason for a REST URI to be more than two levels deep?

Yup; for abstraction. I've seen a couple REST APIs that utilise layers of abstraction through multiple URI levels, for example: /vehicles/cars/123 or /vehicles/bikes/123 which in turn allows you to work with useful information pertaining to both /vehicles and /vehicles/bikes collections. Having said that, I'm not a big fan of this approach; you'll seldom need to do this in practise, and chances are you could redesign the API to use just 2 levels.

And yes, as the comments above suggest, in future it would be best to split up your questions into separate posts ;)

1
  • I think my example for question #2 was over simplistic. I need to specify a comparison operator for each field being used to filter the collection, not just one, so in your example it'd have to be something like /collection?field1=someval&field1Operator=gte&field2=someval&field2Operator=eq. Jul 11, 2013 at 12:43
0

For question 2, a different alternative can be more flexible: consider each search a resource that the user builds before using.

lets say you have a "searches" container, there you do a POST /api/searches/ with the query specification on the content. it could be a JSON, XML, or even an SQL document, whatever is easier for you. If the query parses correctly, a new search is created as a new resource with its own URI, let's say /api/searches/q123/

Then, the client can simply GET /api/searches/q123/ to retrieve the query results.

Finally, you can either ask the client to delete the query, or purge it after closing the session.

0

Is it appropriate to mix some sort of action call with a resource URI (e.g. /collection/123?action=resendEmail)? Would it be better to specify the action and pass the resource id to it (e.g. /collection/resendEmail?id=123)? Is this the wrong way to be going about it? Traditionally (at least with HTTP) the action being performed is the request method (GET, POST, PUT, DELETE), but those don't really allow for custom actions with a resource.

Not, it is not appropriate, since IRIs are for identifying resources and not operations (however ppl use this method override approach for a while, in cases when using non POST and GET methods is not supported). What you can do is looking for an appropriate HTTP method, or create a new one. POST can be your friend in these cases (ppl use it if they cannot find an appropriate method and the request is not retrieval). Another approach to make resources from email sending and so POST /emails can send the mails without creating a real resource. Btw. URI structure does not carry semantics, so from a REST perspective it does not really matter what kind of URIs you use. What matters is the meta-data (e.g. link relation) assigned to the links you sent to the clients.

The best idea I've come up with so far is to allow the API user to specify it as an appendage to the field name (e.g. /collection?someField:gte=someval - to indicate that it should return resources where someField is greater than or equal to (>=) whatever someval is. Is this a good idea? A bad idea? If so, why? Is there a better way to allow the user to specify the type of comparison to perform with the given field and value?

You don't have to create an own query language. I would rather use an already existing one and add some query description to the link meta-data. You should use probably an RDF media type (e.g. JSON-LD) to do that or use a custom MIME type (afaik there is no non-RDF format which supports this). Using existing standards decouple your client from the server, that's what the uniform interface constraint is about.

It would be equivalent to /dogs?person=123. Is there ever really a good reason for a REST URI to be more than two levels deep (/collection/resource_id)?

As I previously mentioned, the URI structure does not matter from a REST perspective. You could use /x71fd823df2 for example. It still would make sense to the clients because they check meta-data assigned to the links and not the URI structure. The main purpose of URI is identifying resources. In the URI standard they state that the path contains hierarchical data and the query contains non-hierarchical data. But it can be very subjective what is hierarchical. That's why you meet multiple levels deep URIs and URIs with long queries either.

I've searched relentlessly for many hours but haven't found answers to my questions anywhere (maybe I just don't know what to search for?).

You should read at least the REST constraints from the Fielding dissertation, the HTTP standard, and probably 3rd generation web APIs from Markus.

Not the answer you're looking for? Browse other questions tagged or ask your own question.