17

I'm designing a new web application which is powered by a REST backend and HTML+JS frontend.

There's one POST method on it to change one entity (let's call Config), that has several side effects in the state of many elements of the application. Let's suppose the POST is performed this way:

POST /api/config BODY {config: ....}

Because of this, I would like to show a preview before those changes are made, for the end user to be able to notice what's going to change.

The thing I first thought about is to make a GET endpoint for the preview, sending the body of the new state of the entity. This way:

GET /api/preview/items BODY {config: ....}

Might show the new state for the items with the new configuration.

GET /api/preview/sales BODY {config: ....}

Might show the new state for the sales with the new configuration.

It seems a good idea to use the GET verb as I'm not altering the state of the application. However, the use of a request body with GET requests seems to be discouraged.

Is there any good practice about this? Other choice might be to store the config as a draft with one method and display the results with others, but it would require an additional step and having to manage the drafts in the server:

POST /api/preview/config BODY {config: ....}

GET /api/preview/items?idPreviewConfig=1
  • What exactly could be this config and how does it affect the items or sales? Does it affect the representation of the returned entity? – Andy Jul 14 '17 at 10:12
  • Let's suppose items and sales both get affected by the changes you make in the config. – Xtreme Biker reinstate Monica Jul 14 '17 at 10:24
  • But what do the changes mean? Does it change the set of returned entities? Does it change the returned structure? – Andy Jul 14 '17 at 10:29
  • Actually it changes the values for items and sales (not the structure), depending on the config you POST. – Xtreme Biker reinstate Monica Jul 14 '17 at 10:33
  • And how large is the config exactly? Can in grow up to several hundred kilobytes or even more? – Andy Jul 14 '17 at 10:35
27

This is too domain-specific to have a native support in HTTP.

Instead, you may do one of the following:

  1. Have a POST /api/config/preview. At server side, the application will know that it shouldn't modify the actual configuration, but combine the actual one with the one you posted, and return the result indicating what was changed.

    Later, if the user is satisfied with the result, she will perform a POST /api/config containing the same payload as in the previous request. This will effectively overwrite the configuration.

    The benefit of this approach is that you'are not making any breaking changes to the current API. Clients who don't need the preview feature would still be able to update the entries as they did before.

    The drawback is that when the body is large, it would mean that it would be needed to send it twice to the server. If this is your case, you may use the next approach.

  2. Have a POST /api/config/prepare which remembers what was sent in a temporary record and returns two things: the ID of the temporary record (for instance 12345) and the preview of the changes.

    If the user is satisfied with the result, she will perform a POST /api/config/commit/12345 to definitively store the changes. If not, the temporary record may be kept for some time, and then discarded by a cron job.

    The benefit is that, here again, you may keep the original POST /api/config intact, and the clients which don't need a preview will not break.

    The drawbacks are that (1) handling the removal of temporary records can be tricky (what makes you think that one hour is enough? What if ten minutes later, you run out of memory? How clients handle a HTTP 404 when doing a commit of a record which expired?) and that (2) two-steps submission of a record may be more complicated than it needs to be.

  3. Move the preview logic on client side.

  • What about Sending a header that says "dont persist this, only show me the what-if"? I'll edit that into the answer if thats fine with you @ArseniMourzenko – marstato Jul 14 '17 at 10:24
  • 1
    @marstato: personally, I'm not particularly fond of HTTP headers for that usage. Although it may make sense for other people, so I'm fine if you edit my answer. Note that you can also post your own answer, which would allow others to upvote it (and will get you reputation points). – Arseni Mourzenko Jul 14 '17 at 10:31
  • I guess option 1 suits better for my case. So you POST the preview config and you have the changes in the result, instead of having to define preview endpoints for each of the defined entities. Seems reasonable. The only thing is you're using a POST to make no change in the server, technically speaking. Option 3 is non-viable in my case. – Xtreme Biker reinstate Monica Jul 14 '17 at 10:31
  • 1
    @PedroWerneck Can you expand on that? It seems to me that option 2 defines another entity (a draft config) and provides stateless ways to interact with them. – Andrew says Reinstate Monica Jul 14 '17 at 18:51
  • 1
    @PedroWerneck It's stateful in the same way storing a config on the server is stateful. So the application is already stateful from your perspective and so are all the options to add this feature. – jpmc26 Jul 15 '17 at 2:43
10

The point of using specific HTTP verbs for different api calls in REST is to leverage the existing HTTP mechanics and expectations.

Using a GET in this case seems to go against both.

A. The client needs to include a body with a GET? unexpected

B. The server returns a different response to a get depending on the body? breaks spec and caching mechanics

If you are struggling with RESTful questions, my rule is to ask myself.

"How is this better than just using POST for everything?"

Unless there is an immediate and obvious benefit, go with the Just Use POST Stupid (JUPS) strategy

  • Hahaha good catch – Xtreme Biker reinstate Monica Jul 14 '17 at 13:14
  • @Ewan...regardless of whether or not this is a pragmatic approach...if you're using POST for everything it should be noted that it's not actually RESTful. – Allenph Jul 14 '17 at 20:40
  • 1
    well, unless POST is the appropriate choice for all your methods. And it's not like there is an objective rule you can apply, we would just be arguing our subjective interpretations of what is little more than a guideline. – Ewan Jul 14 '17 at 21:12
6

You can send a header that indicates to the server "do not persist this, only show me what the result would be if you did". E.g.

POST /api/config HTTP/1.1
Host: api.mysite.com
Content-Type: application/json
Persistence-Options: simulate

{
   "config": {
      "key": "value"
   }
}

To which the server could respond:

HTTP/1.1 200 OK
Persistence-Options: simulated
Content-Type: application/json

-- preview --

Note that, if you use a Unit of Work based O/RM and/or per-request transactions with your DB you can easily implement this functionality for all your endpoints without requiring work on any particular endpoint: If a request comes in with that option, roll back the transaction / unit-of-work instead of commiting it.

  • tools.ietf.org/html/rfc6648 – Peter Rader Jul 14 '17 at 21:59
  • @PeterRader good point, removed the X- – marstato Jul 14 '17 at 23:34
  • Your welcome. Would you say a entity under simulation should be represented as "under simulation"? – Peter Rader Jul 15 '17 at 7:11
  • No; thats the point of a simulation, isnt it? The header value could also be none but that would - for my taste - contradict too much with the nature of the POST method. – marstato Jul 15 '17 at 11:37
2

I would suggest treating this the same way you treat searches. I would set up a POST endpoint at /api/config/preview which CREATES a new preview. Then I would set up a PUT or PATCH endpoint at api/config depending on whether you intend to edit the current configuration, or simply replace the entire config (presumably in the former case you would be sending the preview you just created).

0

Along with the other good answers, another option could be to post the config like mentioned, and have a rollback process available too. I think, like Agile methodology, it's better to be less scared of changes by having more granular, repeatable, and tested procedures, and this would give you a backup when you need it, reducing the risk to little or none, depending on the application.

Then again, if you may have configuration errors affecting the whole system, you would like to handle it more actively, and if that's the case, why not just put the effort into previewing the changes at that point, from a server or client perspective. Although, I can see how this previewing feature could be more expensive to develop, at the uses cases have their own set of disparate steps to follow and test.

0

RFC6648 deprecates new X- constructs so I must vote against the idea to send a new header-field. REST is a architecture style, what we talk about is RESTful - but lets ignore that for this moment.

Because REST is Representative (and a simulation has no representation in the reality) and Stateful (and a simulation is not a state until its committed) we must have a new scope, like a simulation-scope. But we must call it emulation instead of simulation because simulation includes the process of simulation but stateful means we have a standing state, a ideal solution of a simulation: a emulation. So we need to call it emulation in the URL. This might also be a good solution:

GET  /api/emulation - 200 OK {first:1, last:123}
POST /api/emulation/124 - 200 OK
GET  /api/emulation/124/config - 200 OK {config:{tax:8}}
PUT  /api/emulation/124/config {config:{tax:16}} - 200 OK {config:{tax:16}}
GET  /api/emulation/124/items - 200 OK [first:1, last: 3000]
GET  /api/emulation/124/items/1 - 200 OK {price:1.79, name:'Cup'}
--- show emulation ---
--- commit emulation ---
PUT /api/config {config:{tax:16}}
DELETE /api/emulation/124 - 200 OK

There is another approach .... you might noticed having alot of requests from the HTML/JavaScript-client may produce the Too many requests, what reaches the limit of about 17 requests at the same time (have a look at this page). You can swap the usage of REST and instead of deliver lame object-states you can deliver rich user-specific page-states. Example:

GET /user/123/config - 200 OK {user:'Tim', date:3298347239847, currentItem:123, 
                  roles:['Admin','Customer'], config:{tax:16}, unsavedChanges:true, ...}

Kind Regards

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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