Pregunta

I was reading an article on HATEOAS and while I understand the idea of providing the URLs for further actions in the response, I don't see where you specify what HTTP verbs should usedto interact with those URLs.

For example, from What is HATEOAS and why is it important for my REST API?, how from this response

GET /account/12345 HTTP/1.1

HTTP/1.1 200 OK
<?xml version="1.0"?>
<account>
    <account_number>12345</account_number>
    <balance currency="usd">100.00</balance>
    <link rel="deposit" href="/account/12345/deposit" />
    <link rel="withdraw" href="/account/12345/withdraw" />
    <link rel="transfer" href="/account/12345/transfer" />
    <link rel="close" href="/account/12345/close" />
</account

do you know if I should issue an HTTP PUT or POST to /account/12345/close?

¿Fue útil?

Solución 2

Your question has a lot of answers on Stackoverlow, but most of them skirt the reason you are asking, and I suspect you always find them partially disatisfying.

If we take Roy Fielding at his word, it is impossible to write most commercial interactive Client-apps as SOA RESTful/HATEOAS using HTTP/HTML. It might be possible in other mediums, I can't say.

So the practical answer is "look it up in the documentation" and "write your Client with that app-knowledge in it" with a side-helping of "ignore the fact that we're breaking Fielding's rules by doing that".

I tend to design JSON responses that provide this approach:

GET /account/12345 HTTP/1.1
{
    "account": {
        "number": "12345",
        "currency": "usd",
        "balance": "100.00",
        "deposit": {
            "href": "/account/12345/deposit",
            "action": "POST"
        },
        "withdraw": {
            "href": "/account/12345/withdraw",
            "action": "POST"
        },
        "transfer": {
            "href": "/account/12345/transfer",
            "action": "POST"
        },
        "close": {
            "href": "/account/12345/close",
            "action": "DELETE"
        }
    }
}

... adding additional properties to the design as needed, but these are the basics.

I believe this allows the consuming Client(s) to be written in a RESTful way, but in so doing I am using the Response Body, which Fielding says is not what he intended.

I'd offer this explanation seperate to the answer though:


Fielding says "I am getting frustrated by the number of people calling any HTTP-based interface a REST API." (http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven).

Note how he so stridently says "any" HTTP-based interface.

The most relevant segment in his 'lecture' is this:

"A REST API should be entered with no prior knowledge beyond the initial URI (bookmark) and set of standardized media types that are appropriate for the intended audience (i.e., expected to be understood by any client that might use the API). From that point on, all application state transitions must be driven by client selection of server-provided choices that are present in the received representations or implied by the user’s manipulation of those representations. The transitions may be determined (or limited by) the client’s knowledge of media types and resource communication mechanisms, both of which may be improved on-the-fly (e.g., code-on-demand). [Failure here implies that out-of-band information is driving interaction instead of hypertext.]"

He says this because an HTTP/HTML app URI media-type is just "text/html", and where is the Verb in that? There isn't one. A URI cannot tell you what Verb something requires for use/navigation, ergo you cannot use just the in-band data alone to construct the 'next' navigation dynamically in your Client.

He explained that he believes we render our URI's as part of CDATA that includes the "method" or that the context of the URI would self-evidently provide it, like the FORM element does. He explicitly rails against the OpenSocialst REST API stating that it is not RESTful.

Here: “anchor elements with an href attribute create a hypertext link that, when selected, invokes a retrieval request (GET) on the URI corresponding to the CDATA-encoded href attribute.” Identifiers, methods, and media types are orthogonal concerns — methods are not given meaning by the media type. Instead, the media type tells the client either what method to use (e.g., anchor implies GET) or how to determine the method to use (e.g., form element says to look in method attribute). The client should already know what the methods mean (they are universal) and how to dereference a URI.

Note that he says the Client should already know what the methods mean, he does not say that the Client should already know what they are - which is why you asked your question. A lot of people struggle with this, because we don't in fact build our apps like this in most SOA environments.

Like a lot of engineers, I just wish Fielding would come out with a clarification or re-statement, but not only has he not done so, he has published two further admonishments to us as engineers, doubling-down on his statement, saying that we should stop calling our API's RESTful and accept that we're building RPC's.

I think the JSON elements-like approach is a reasoble bridge, but I have no answer to the fact that we're using the Request Body to do it, not relying on the Media Type to imply it.

Finally, there is a newer Verb in HTTP called OPTIONS which, for a given URI, would return the allowed Verb actions list. I think Fielding had a hand in writing this HTTP revision. That would allow a Client to generically construct URI navigations without the forbidden internal app knowledge. But that has three issues I can think of in the practical world:

  1. You would have to code a mechanism into your Service Aggregation to make that call for every URI you try to return, and since much data contains many URI's (_links in HAL) that adds a lot of extra 'hops' into your Service Response construction. We'd probably all complain about that.
  2. Virtually no SOA site claiming to be RESTful actually implements an OPTIONS verb-method-call for you to do this enquiry with anyway.
  3. We would all complain about the 'unecessary' extra calls it adds (especially in the eCommerce world) to a Client's processing and its tendency to push us beyond SLA requirements.

Otros consejos

Don't puts verbs in your URIs (eg /account/12345/transfer). URIs represent resources, not actions.

The verbs to use are defined by the HTTP protocol (eg GET, POST, PUT, OPTIONS, DELETE etc). REST is a architecture design with a set of constraints, and HTTP is a protocol that adheres to these constraints. HTTP defines a limited set of verbs to transfer the state of a resource from client to server and vice versa. By definition you are constrained to these verbs only.

The client should decide what HTTP verb to use based on what it is trying to do. The server doesn't need to tell it what verbs there are, it already knows based on the HTTP protocol.

If the client needs to know what verbs it can use on a resource it can query the resource using the OPTIONS verb and look at Allow header in the response (assuming the server returns this information, which it should if it is being helpful). Some resources might only accept GET, while others may accept others such as POST and PUT.

Have a look at the HTTP specification to see what verb to use in what context.

To give an example from your original post. Say you have an account resource with a URI at

/accounts/12345

and you want to close the account. Remember REST is state transfer. The client is closing the account so it has the account in a state of closed on its end. It then transfers that state to the server so that the client and server both are in line with each other. So you PUT the clients state (which is the resource in a closed state) onto the server

PUT /accounts/12345

The body of the request should contain a representation of the resource in a closed state. Assuming you are using XML to represent the account resource it would be something like this

PUT /accounts/12345

<?xml version="1.0"?>
<account>
    <account_number>12345</account_number>
    <balance currency="usd">100.00</balance>
    <state>closed</state>
</account>

The resource on the server now mirrors the resource on the client. Both are in a closed state. If you don't want to transfer the whole resource every time you make a change to one of its attributes you could split them out into a resource hierarchy. Make the status of the account its own resource and PUT to that to change it

PUT /accounts/12345/status

<?xml version="1.0"?>
<state>closed</state>

do you know if you should PUT or POST to /account/12345/close?

You consult the documentation for the API, that's how you know. HATEOS is not a replacement for formal documentation. Documentation is necessary for REST APIs just like any other API.

HATEOS lets you know what your other options are from a specific resource. It doesn't tell you why you would use those options, or what information you would send them. Content Types only express syntax and high level semantics, not application level semantics, so they're not documentation either.

If you want to know how to use a REST API, read the documentation. If you want someone else to use your REST API, provide them with documentation.

There's no magic here.

@Cormac Mulhall's answer is very good, but I'd like to suggest a refinement that I heard from a colleague:

Actions or events that happen to a resource can be treated as subordinate domain nouns, using the gerund form of the action verb or the event name, but should be placed under a meaningful path identifier such as "actions" or "events" or something similar. The resource representation that will be returned expresses state data about the action, so that POST or PUT operates as a request.

Suppose that orders have several lifecycle states. At some point after being drafted, an order is placed, fulfilled, or cancelled.

Information about these order actions would be located by putting the action name in plural noun form under the resource path with /actions to return details if the actions state is active, or 404 NOT FOUND otherwise.

https://order.api.foobar.com/v1.0/orders/{orderId}/actions/placements
https://order.api.foobar.com/v1.0/orders/{orderId}/actions/fulfillments
https://order.api.foobar.com/v1.0/orders/{orderId}/actions/cancellations

When these actions are idempotent (an order cannot be placed twice in a row), so these actions can be requested by PUT’ing of the appropriate representation to these URIs. When they are not idempotent, the are created by POST’ing to the plural form.

For example, to track approvals order, we could POST to:

https://order.api.foobar.com/v1.0/orders/{orderId}/approvals

and then we see information about individual approvals by doing a GET against:

https://order.api.foobar.com/v1.0/orders/{orderId}/approval/1

It is often useful to use an aggregate called something like "actions" to find all actions:

https://order.api.foobar.com/v1.0/orders/{orderId}/actions

We could POST to this, letting the representation declare what type of action is meant.

You can also get a list of actions across individual orders by leaving the {orderId} parameter off:

https://order.api.foobar.com/v1.0/orders/actions/placements
https://order.api.foobar.com/v1.0/orders/actions

These can be searched by adding query parameters:

https://order.api.foobar.com/v1.0/orders/actions/placements?since={sinceTimestamp}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top