HATEOAS - How to model link relations that change state

对着背影说爱祢 提交于 2019-11-30 04:18:58

问题


Following HATEOAS principles that each states should be hyperlinked, what is the best way to model links that change resource state?

Let's take classical example with orders:

{
   id : 12,
   state: 'pending',
   ...,
   links: [
     ...,
     { 
       rel: 'cancel',
       href: '/orders/12/cancel'
     },
     ...
   ]
}

I am not totall happy with that "/cancel" part - I would feel a lot better if I could send "PUT" request with contents:

{
   status:'cancelled'
}

But how do I represent that with "href" attribute in links section? I would like to represent available actions there since, for example, cancelling an order isn't always possible ('completed' state).

One possibility would be to use URL like '/orders/12?action=cancel' what it kinda feels like RPC approach and that I am missing something.

Another possibility that looks probably nicest, would be to have links like that:

{
  rel: 'cancel',
  href: '/orders/12/',
  type: 'PUT',
  values: {
    state: 'cancelled'
  }
}

This solution maybe feels a little bit verbose.

Any ideas how to handle that gracefully? Maybe someone has already solved similar "problem"?


回答1:


Modelling resources is the most difficult part of REST. Strictly adhering to the standard means if you see yourself ever doing this: /resource/:id/{action}, you're violating the "using HTTP correctly" criteria, as your endpoints should ideally always be "nouns', never "verbs" (the verbs are what the HTTP protocol provides).

So, while "it depends" (ie. the hard part of designing resources), typically: Object model states can be considered as resources themselves.

Which means, your order status is actually a resource you can query (either as a standalone /orderstatuses resource or as a sub resource eg. /orders/:id/status)

Your Application State can now link to the status resource based on the current status of the order itself. If your 'status' schema looks something like this (pseudo):

key: 'status'
values: ['pending', 'cancelled']

Your app could then PUT /order/:id/status {status:'cancelled'} (a well formed status) back to the API, which would then act to cancel your order. It's a little weird thinking in these terms (RPC is a lot more intuitive), but hopefully this helps.




回答2:


You have to describe forms somehow. You "verbose" solution is perfectly okay:

{
  rel: 'cancel',
  href: '/orders/12/',
  type: 'PUT',
  values: {
    state: 'cancelled'
  }
}

note: you have to define a custom MIME type or use a generic MIME type which is capable of describing forms (e.g. collection+json), or which an RDF type (which supports REST vocabs like Hydra) - aka. uniform interface / self-descriptive messages

I would like to represent available actions there since, for example, cancelling an order isn't always possible ('completed' state).

If an operation is not available, then don't send a link pointing to that operation.




回答3:


I would suggest either of these two models. The first is the classic one, but with rel="edit-form" and using PATCH where available. The second is an alternative which comes about through some lateral thinking about how the HTTP resource model maps onto your application domain model (namely, that the two don't have to have a 1:1 mapping).


Solution 1

Edit the resource in-place.

HTML compatible:

HTTP/1.1 200 OK
Content-Type: text/html
Location: /orders/1/

...<a rel="edit-form" href="./edit">Edit</a>...

 

HTTP/1.1 200 OK
Content-Type: text/html
Location: /orders/1/edit

...
<form action="../" method="POST">
    <input type="hidden" name="_METHOD" value="PATCH">
    <button type="submit" name="status" value="cancelled">Cancel Order</button>
</form>
...

 

POST /orders/1 HTTP/1.1
Content-Type: application/x-www-form-urlencoded

_METHOD=PATCH&status=cancelled

Rich client (e.g. HTML+Javascript) compatible:

PATCH /orders/1 HTTP/1.1
Content-Type: application/x-www-form-urlencoded

status=cancelled

and/or

PATCH /orders/1 HTTP/1.1
Content-Type: text/json

{
    "status": "cancelled"
}

the _METHOD key is a well-known means of providing REST frameworks with the correct method due to HTML's lack of support for HTTP.


Solution 2

Or, Delete the resource (and, incidentally, create a new one)

DELETE /orders/1 HTTP/1.1

 

HTTP/1.1 201 Created
Location: /cancelled-orders/1

For more info on this way of mapping web resources to domain objects, please see my answer to a similar question.

Another answer you may wish to read is this one.




回答4:


Further to papercowboy's answer, where status transitions are not always available you can document what is currently possible as a resource, e.g.

/order/:id/availableStates

{
    "availableStates": [
        {"status": "cancelled"}
    ]
}


来源:https://stackoverflow.com/questions/17529315/hateoas-how-to-model-link-relations-that-change-state

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!