3 ms·
Maybe it would make sense if you actually looked at it like a domain. I'm not sure of a domain where an operation cannot be represented as one of: * Return $d
by warfangle 12y ago
Maybe it would make sense if you actually looked at it like a domain.
I'm not sure of a domain where an operation cannot be represented as one of:
* Return $data
* Return a manipulation of $data that is the same type as $data
* Return a value derived from $data
* Update $data with a new value
* Create a new identifier with values that conform to the same type as $data
* Delete $data from the system
Certainly there are things like transcoding a streaming video which don't seem to map immediately, but they could if you shoehorned them. You're probably better off using something like UDP for that, though, instead of an HTTP request over TCP/IP.
Let's take images. Translation factor:
worzworts -> image (identity)
flubbleize -> encode to jpeg (non destructive manipulation)
mrefgle -> encode to png (non destructive manipulation)
chezzle -> execute image as a Piet program and return the output (derived value that is not necessarily of the same type)
Now, it's think through it a little.
POST /image/ <http://imgur.com/8XA9Eva> => 1
GET /image/1 -> identical response (except some http headers) to the imgur URL
GET /image/1/piet -> "Piet"
GET /image/1/jpeg -> /image/1 re-encoded as a jpeg
All good here. But let's look at some of the operations you suggest might be used, and the action they take:
POST /image/1/jpeg -> re-encodes the gif as a jpeg and saves it to /image/1; does not conform to REST best practices because POST is for creating new records -- immediately the developer is confused
GET /image/1/piet -> <unknown, I do not have a Piet runtime> because jpeg encoding is lossy, it fundamentally changes the data source at /image/1 with an arbitrary filter; in this case, JPEG compression.
Funky undefined behavior, and it may not have the side effects intended. Now let's see how it would work RESTfully:
GET /image/1/jpeg -> returns a jpeg encoding of /image/1
POST /image/ <response from /image/1/jpeg> -> 200 OK, { id: 2 }
POST /image/ "Piet" -> Error 415 (images expect an image, not a string)
GET /image/2 -> returns jpeg encoding of /image/1
GET /image/1/piet -> "Piet"
PUT /image/2 <response from /image/1/png> -> 200 OK
GET /image/2/piet -> "Piet" (since png is a lossless encoding, Piet will perform the same on it)
If you conform your API to single responsibilities, you won't confuse the consumers of it by transforming a data source in-place on their request.
Things go a little sideways, but not much, if these operations need parameters. But that's why they're query parameters and not a part of the url. I don't think anyone would suggest that you create a URL like
/image/1/jpeg/width/640/height/480
That's just silly. If you follow the single-resource-deep philosophy, though, anything after the ID is a resource that is derived from the resource residing at #ID. This URL might actually make a lot of sense:
/image/1/jpeg/resize/640x480
But then again, so would:
/image/1/resize/640x480/jpeg
But wait, there's an even better way to define these ... and still be restful.
/image/1.jpeg, /image/1.png, /image/1.piet
Oops, that last one doesn't work. Piet isn't an image format, it's the result of executing the image as a Piet program. So maybe that one works better as a URL segment:
/image/1.jpeg (image), /image/1.png (image), /image/1/piet (string)
In this case,
/image/1.jpeg?w=640&h=480
makes just as much sense as
/image/1.png?w=640&h=480
But since we know that instead of a transformed image, the image/:id/piet resource is a derived string based on image/:id, this URL totally doesn't make sense - and neither should it! Should the image be resized before being executed? How exactly does one change the height of an ASCII string? Maybe you want an image of the ASCII string that has been resized? Ambiguous request! Undefined behavior!
/image/1/piet?w=640&480 -> 400, bad request.
But these same operations could be done on a video stream. POST creates a new video stream identifier; streaming source opens a websocket/webrtc/whatever based on that identifier; GET /stream/1/hls -> hls chunked stream of the incoming source video; GET /stream/1/dash -> DASH chunked stream of the incoming source video. Uh oh, source stream disconnects due to a network glitch. That's okay, they can just re-open it with the same identifier. Source stream PUT <some kind of manifest> /stream/1 : notifies the server that it is complete, server can cease to accept new streaming inputs for /stream/1; GET /stream/1/hls now returns a 301 redirect to /video/1/hls, which contains the entirety of the video that was streamed, as the server received it.
Or do you have some other domain in mind that doesn't involve flubber?
- lmm 12y agoYou've made everything non-destructive in your example, which I think makes REST a better fit - although even then, note how your example requests are almost all GET or POST. > does not conform to REST best practices because POST is for creating new records -- immediately the developer is confused You've got it backwards. The developer doesn't start with that request and try to figure out what it does. The developer starts by wanting to e.g. convert an image to gif, and trying to figure out what call will let them do that. POST /image/ <response from /image/1/jpeg> -> 200 OK, { id: 2 } PUT /image/2 <response from /image/1/png> -> 200 OK Notice how you're offering the same operation through two different verbs here - exactly what the article is advocating! The specific domain where I've seen REST go most wrong is contracts for large scale commercial insurance / reinsurance. Operations are things like: * Split a contract into multiple loss layers (0-25%, 25%-50% etc.) * Reshuffle the contract hierarchy - e.g. change a ship insurance contract from being split into hull / cargo and then individually layered in them, to being split into layers and then each of those split hull / cargo * Compare two quotes, potentially made against different versions of the contract All these are things you can encode as REST operations - you can think of particular things to be entities and declare each of these to be creating or deleting entities. But that's in the same sense that you can encode any data as binary. What we found was that users - people with actual domain knowledge - didn't think in terms of those entities. They thought in terms of these operations, which had their own names that everyone in the industry understood, and the only entity they thought of was the single "current version of the contract" - everything else was one of several different kinds of "change the contract" operation. So the only mapping that made sense to a client familiar with the domain was a bunch of different POST operations on a contract object.