23
votes

So far as I know, four kind of methods are used in RESTful APIs:

GET for getting the resource.
POST for updating the resource.
PUT for creating or substituting the resource.
DELETE for deleting the resource.

Assume we have a resource named apple, and we can 'update' it in several ways. For example, pare it, slice it, or make it apple juice.
Each of these three different updating actions takes different arguments, and of their APIs, the common part will be:

POST /apple HTTP/1.1
Host: www.example.com

<different combination of arguments>

In this situation, three APIs share the same URI and the same request method, the only differences of them are arguments. I think this forces the backend to be ready for accepting the union set of those arguments, and to distinguish which action is actually requested, the backend need to check out the combination of the arguments. It's so much complicated and not graceful.

So my question is:
In this apple cases, how to work out an elegant set of RESTful APIs which make the backend easily handle with it.

4

4 Answers

18
votes

First of all, try to avoid conflating HTTP methods to CRUD operations. I believe that's the main source of confusion in REST. HTTP methods don't translate to CRUD operations cleanly like that. I have a detailed answer here:

S3 REST API and POST method

In short.

  • POST is the method used for any operation that isn't standardized by HTTP, and subjects the payload to the target URI.
  • PUT is used to completely replace the resource at the present URI, and subjects the payload to the service itself.
  • PATCH is for partial idempotent updates, with a diff between the current and the desired state.
  • DELETE is used to delete the resource.
  • GET is used to retrieve the resource.

Now, on the backend side, try to think of REST resources more like a state machine where you can use the methods to force a transition rather than an object with methods. That way you focus the implementation on the resource itself, not on the interaction with the protocol. For instance, you may change an object's attributes straightforwardly from the method's payload, and then have a method that's called to detect what transition is needed.

For instance, you may think of an apple as having three states, whole, pared, sliced and juiced. You transition between states by using the standardized behavior of the methods.

For instance:

GET /apple 

{"state": "whole",
 "self": "/apple"}

Then you want to slice it. You may do something like:

PUT /apple

{"state": "sliced"}

Or you may do something like:

PATCH /apple

{"from_state": "whole", "to_state": "sliced"}

Or even something like:

POST /apple

{"transition": "slice"}

The idea is that the implementations can be generic enough that you don't have to worry too much about coupling the resource to the HTTP methods.

  • The PUT version is idempotent, so your clients can choose to use it when they need idempotence.
  • The PATCH version guarantees the client knows the current state and is trying a valid transition.
  • The POST version is the most flexible, you can do anything you want, but it needs to be documented in detail. You can't simply assume your clients will know how the method works.

As long as your implementation of the resource understands that when apple.state is changed to something else it should detect what change occurred and perform the adequate transition, you are completely decoupled from the protocol. It doesn't matter what method was used.

I believe this is the most elegant solution, and makes everything easier to handle from the backend side. You can implement your objects without worrying too much about the protocol. As long as the objects can be transitioned between states, they can be used by any protocol that can effect those transitions.

5
votes

My RESTful HTTP API is rather different from yours. I have:

GET for getting a resource.
POST for appending a new resource to a collection.
PUT for substituting a resource (including truncating collections).
DELETE for deleting a resource.
PATCH for updating a resource.
LINK for indicating a relationship between two resources.
UNLINK for removing a relationship between two resources.

A ‘leaf’ resource can be thought of as a collection too.

For example, say you have /fruits and you POST an apple to that collection resource, that returns

201 Created
Location: /fruits/apple

In the same way, you can treat /fruits/apple as a collection of its properties, so:

GET /fruits/apple
->
colour=red&diameter=47mm

GET /fruits/apple/colour
->
red

GET /fruits/apple/diameter
->
47mm

and therefore:

PUT /fruits/apple/slices
"12"
->
201 Created

GET /fruits/apple
->
colour=red&diameter=47mm&slices=12

So in summary, I would recommend representing your actions as nouns, and locate those nouns as sub-resources of the resource you want to apply the action to.

1
votes

Think in terms of resources. Here Apple is a resource.

To add one or more apples to list "/apples", use POST. REST style allows posting array.

POST /apples HTTP/1.1
Host: www.example.com

Now suppose you have an apple with ID 123. You can get details using method GET on "/apple/123".

GET /apples/123 HTTP/1.1
Host: www.example.com

To make any change to apple 123, just POST to it directly.

PUT /apples/123 HTTP/1.1
Host: www.example.com

Pare it, slice it, or make it apple juice - all these are basically changing some attributes of apple 123. As you were saying (rightly), PUT different combination of attributes.

0
votes

I think this is up to the implementor to decide, but I see two approaches. Strictly from a single responsibility perspective it may make sense to provide separate services for these distinct operations.

However if you insist on a single service of all I guess you can pass an object with a action type qualifier to make it easy to delegate the request to different code in the service. The single object can then have other optional parameters to support the data needs of each operation.