1

I'm learning how to design a RESTful API and I've come across a quandary.

Say I have a POST endpoint to perform an action. The action has a certain cost associated with it. The cost depends on what the action is, particularly, on the body of the POST. For example, given these two requests:

POST /flooblinate
{"intensity": 50, "colorful": true, "blargs": [{"norg": 43}]}

POST /flooblinate
{"intensity": 100, "colorful": false, "blargs": []}

Say the first one costs 500 and the second one costs 740.

I want to create a method which will tell me what the cost of posting the action will be. Since I am not creating or updating anything, it seems that GET is the most appropriate verb. However, a request body with GET should not have any meaning. This means that I'd have to put the data in the query string, say by URL encoding the request body to be passed to the POST:

GET /flooblinate/getCost?body=%7B%22intensity%22%3A+50%2C+%22colorful%22%3A+true%2C+%22blargs%22%3A+%5B%7B%22norg%22%3A+43%7D%5D%7D

This seems less than ideal since it's two data formats for the same thing. But the following:

POST /flooblinate/getCost
{"intensity": 50, "colorful": true, "blargs": [{"norg": 43}]}

This also seems less than ideal since it's abusing the POST verb to query information, instead of to create or update something.

What's the correct choice to make, here? Is there any third alternative? Is there a way to rethink this design fundamentally which will obviate the need to make this choice?

Community
  • 1
  • 1
Claudiu
  • 206,738
  • 150
  • 445
  • 651

2 Answers2

1

Personally I'm not for adding dryRyn flags. I try to avoid boolean flags in general unless they're really required.

I've two ideas to cover this scenario:

  1. One is to introduce state on the backend site, e.g. STARTED, FINISHED. When given resource action is submitted it has STARTED state and calculated cost which cam be viewed via GET method. Such resource may be modified with PUT and PATCH methods and is submitted when given method changes its state to FINISHED. Resources that didn't change its state for given amount of time are removed are their state is changed to other terminal value.
  2. Second idea is to introduce a new endpoint called e.g. /calculations. If you need to calculate the cost of given action you just send the same payload (via POST) to this endpoint and in return a cost is given. Then resource send may be kept on server for some established TTL or kept forever.

In all the scenarios given (including yours) there's a need to make at least two requests.

Opal
  • 70,085
  • 21
  • 151
  • 167
  • #1 sounds like the most RESTful approach. It'd be more work on the back end to support that but it may be the nicest overall. About #2, isn't it a misuse to use POST just to query data? As in, shouldn't it be a GET? POST is *"The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line. "*, while GET is *"The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI."* (from RFC2616, section 9) – Claudiu Oct 21 '15 at 16:48
  • Also why do you try to avoid boolean flags in general? – Claudiu Oct 21 '15 at 17:31
  • When it comes to boolean flags I supported [anti-if-campaign](http://antiifcampaign.com/) some time ago and I strongly associate a random boolean flag with it. So, since I associate boolean with if statements I try to avoid them unless impossible. – Opal Oct 21 '15 at 19:40
  • 1
    When it comes `/calculations/` endpoint I don't find as being not RESTful. The endpoint (mind the plural to mimic a collection) and communication with it is designed in such a way to behave as a normal resource even though it does an operation. That's what I wrote: a `POST` is sent to **create** a new calculation resource that returns 201 along with the data and appropriate Location header. After the action you mentioned is undertook, calculation resource may be removed. – Opal Oct 21 '15 at 19:52
0

The nicest choice here seems to be to have the endpoints return the info that need querying, and add a dryRun parameter to those endpoints. Thus:

POST /flooblinate?dryRun=true
{"intensity": 50, "colorful": true, "blargs": [{"norg": 43}]}

Returns:

{"cost": 500, /* whatever else */

And then posting with dryRun=false actually commits the action.

Claudiu
  • 206,738
  • 150
  • 445
  • 651