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!
Why not both?
Which is to say, yes, there are trade offs to consider, but if the marginal cost of implementing a second option is small, you can offer to your clients the ability to select which representation they prefer, so that they can choose their own trade offs (of course, there's some complexity penalty to be paid by offering a choice, rather than solving "the" problem for the clients).
The major con I see is that you've built an endpoint tightly coupled to this particular view within this particular application.
Not quite the right language, from a REST perspective. It's not the endpoint that is coupled to the application, but the media type of the representation.
Of course, worrying about media types tends to fall by the wayside when we are implementing both the client and the server, and their release cycles are coupled.
The pros of 2. are that we are using nothing but generic resource endpoints, which could be re-used by many different views and applications.
That thought is incomplete - you can not only reuse the endpoints, but you can re-used the representations themselves... ie: caching. If the client can pull the data it needs out of its own cache, then it doesn't need to round trip at all. Failing that, an intermediate cache may already have a copy of the data, shortening the round trip. The "server" that the client is talking to might be a cache farm in front of your app, keeping the workload low while being able to scale out.
In REST, you want to make sure that your designs take advantage of the uniform interface.
So one of the things you should be thinking about is the cache lifetime of your resources; how long are representations valid? Are other views and applications going to be able to take advantage of that?
Should the fact that this API is an internal company API (and almost certain to remain so), rather than a public facing one, influence my decision?
That's likely to put limits on the volume of traffic you'll need to support. Also, if the clients are all going to be centrally located, then round trip time falls away as a concern as well.
Best Answer
One of the core ideas behind REST is that each URL provides a representation for a single resource (although that resource can be a collection that gives information about other resources).
If you want to get information about multiple sensors, where you don't know/care which device each sensor is connected to, the canonical way is to provide a top-level sensors resource:
GET /sensors
. This would give you all sensors in the system. If you wan't to restrict the list in some way, you use query parameters:would give you the temperature sensors connected to the listed devices.
If you do care which device each sensor is connected to, there is no way around performing multiple requests, one for each device.