Where in a HATEOAS architecture do you specify the HTTP verbs?
Asked Answered
G

4

27

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?

Grafton answered 13/11, 2013 at 16:33 Comment(2)
You should not use you own verbs. You should issue a DELETE on /account/12345, a PUTover /account/12345 with new total, [or better a POST to /account/12345/operations with a proper body.Savor
@Savor - but how do you actually know? You're simply making an assumption that may or may not be consistent with what the API implementor has built.Listerism
A
12

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.

Acceptable answered 9/12, 2017 at 1:21 Comment(3)
I believe in the "REST Misconceptions" talk he means RDF and Dublin Core. So perhaps the dynamics of the concept should be implict in the vocabulary? So if you had a bank account with no overdraft you provisions and one with you'd either need to have separate terms, or bake in the requirement into the vocabulary that they need to check the overdraft policy.Grafton
I did some thinking about the 'media type' aspect of HATEOAS for doing something as simple as paging through a table of results - which could be considered a 'staple' activity for an information system which had representations bound to URIs. HTML sort of provides for that style of interaction: assign a couple of <button type="submit'/> to a <form/> and label them 'Prev' and 'Next' ... each logically bound to a URL that represented the respective rows of the table. But if the 'client' were to be a 'user agent' other than a 'web browser', you'd need some canonicalization of 'pageable list'.Eyeball
@DavidBullock The canonicalization of 'pageable list' is given by the 'next' and 'prev' link types defined in the HTML Specification and registered by IANA.Dravidian
M
35

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>
Mabuse answered 13/11, 2013 at 17:10 Comment(7)
There is a good article here for why you would accept actions modeled as resourcesSherard
I don't really get the point of what they are doing in that article. They say "If you consult the state diagram, you’ll see that all the various possible transitions aren’t communicated at all in the API contract" That is a good thing, the various states a resource can be in shouldn't be in the URL scheme. They seem to be doing this to avoid having to document the states for the client. This is what content types are for. Both the client and the server should be aware of the valid states a resource can be in.Mabuse
Read the article again and in my view they are fundamentally misunderstanding REST. They seem to think you have to provide resources as triggers to move the state on the server. So to move an audit resource to the state of accepted you create a new resource at audits/1/acceptance. No, you just put audit/1 into the acceptance state on the client and PUT it back to the server. You don't (and shouldn't) construct URL end points to trigger different state transitions.Mabuse
The restful objects specification also supports it. I am not saying either is correct, and honestly I am confused by the numerous different mantras available on the web. The topic appears almost to be one of those tabs vs. spaces discussions. The criticism towards actions in the url appears however to very often be it is not REST or it's wrong. I will welcome an article that explains why it is wrong in depth.Sherard
Roy Fielding's thesis that originated the REST concept is a good start to see the Big Idea of REST. The problem with something like audits/1/acceptance is that this is unnecessarily complicating the resource model. The resource acceptance is now a resource that is semantically linked to single state of audit 1. It exists solely to act as a verb to change audit/1. It is not bad to have side effects from changing one resource, but you shouldn't have to change one resource to change another, you should just change the original resource.Mabuse
Likewise with Restful Objects. I don't really see the problem these things are trying to solve, other than to be able to not have to build proper understanding of the resources into both the client and the server. Though I am only reading about Restful Objects now, I haven't see that project before. It seems rather redundant though, if you just build your client and server to be able to understand the resources they are consuming.Mabuse
You should have a look at the discussion here then, look towards the comments by Dan Haywood. I have first hand experience with the added complexity you mention, so that is absolutely a valid point!Sherard
A
12

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.

Acceptable answered 9/12, 2017 at 1:21 Comment(3)
I believe in the "REST Misconceptions" talk he means RDF and Dublin Core. So perhaps the dynamics of the concept should be implict in the vocabulary? So if you had a bank account with no overdraft you provisions and one with you'd either need to have separate terms, or bake in the requirement into the vocabulary that they need to check the overdraft policy.Grafton
I did some thinking about the 'media type' aspect of HATEOAS for doing something as simple as paging through a table of results - which could be considered a 'staple' activity for an information system which had representations bound to URIs. HTML sort of provides for that style of interaction: assign a couple of <button type="submit'/> to a <form/> and label them 'Prev' and 'Next' ... each logically bound to a URL that represented the respective rows of the table. But if the 'client' were to be a 'user agent' other than a 'web browser', you'd need some canonicalization of 'pageable list'.Eyeball
@DavidBullock The canonicalization of 'pageable list' is given by the 'next' and 'prev' link types defined in the HTML Specification and registered by IANA.Dravidian
P
7

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.

Poss answered 13/11, 2013 at 17:34 Comment(6)
Ah, I thought it was supposed to be all self-contained. I'll bold they key part of your answer.Grafton
I should be contained in the HTTP protocol and the documentation for the Content Type you use. It should not be hard coded into the URIs. For example, to write a web browser all I need to know is the HTTP 1.1 protocol and HTML.I don't need to know the specific lay out of the site www.nytimes.com, I can figure that out from the HTTP protocol and the HTML documents they provide. If you need a specific content type you can define your own using the vnd.yourcompany.yourtype Content-Type, which you can then document for clients.Mabuse
I'm meant self-contained in the sense that the response gave enough information on how to use the API as well. Such as if you can only DELETE an account when the balance is exactly 0 you have no way of indicating that in the response because is you are not 0 there are other verbs you can perform on that URL so it gets listed just the same way regardless of whether or not the DELETE would be allowed.Grafton
HTTP follows the "It's easier to beg forgiveness than ask permission" philosophy. Instead of knowing before hand what you can do you try to do something and look at the response to find out why it didn't work. So if you try and DELETE an account but you cannot DELETE it because the balance is not 0 the server should return that information. For example "401 (Unauthorized) You do not have permission to delete non-empty accounts".Mabuse
Of course there is nothing stopping the server giving the client a management resource containing only the links that are possible on a particular resource at that moment. So if you cannot DELETE that account then the HTML page or JSON management page just won't have that link anywhere. But you have to still assume someone might send the HTTP request anyway. The server must take full responsibility for allowing or not allowing a request. You can't rely on a client that will not send requests that the user can't perform.Mabuse
I wanted to make an observation on @Will Hartung admonisment : "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." ... Fielding's thesis, which is the 'bible' for RESTfulness and HATEOAS, would call this "Out of band knowledge", and he defines using out-of-band knowledge as not-RESTful by definition. So yes, in-practice, the statement is what we all do ... but no, by doing it we're not building RESTful apps, Fielding says we're building RPC's.Acceptable
N
4

@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}
Neilneila answered 1/4, 2015 at 20:52 Comment(3)
While this might work, it works against the benefits of RESTful architecture because it couples too tightly the URL scheme with the states the resource can be in. The definition of valid states should be negotiated through the Content Type of the resource representation, rather than the URL scheme. This might involve defining a content type "orders" that the client and server are aware of, but that is outside the scope URL scheme. Dumb content types (such as just 'json') lead to devs putting too much representation logic in the URL. Put it in the content type instead.Mabuse
@CormacMulhall I'm having a hard time understanding how a custom media type can convey the meaning of actions, can you elsborate on how to escape the CRUD pitfall using that approach?Kobi
So the media type doesn't tell the client what actions it can perform on the server. It is instead a contract between the client and server that the representations of the resources shared between them will be in these formats. The client doesn't ask the server what actions can I perform on you, the client instead updates the resource, following the content type spec, to a valid new state and then simply PUTs that back to the server. Because they are both working off the same content type the server should also understand this new state.Mabuse

© 2022 - 2024 — McMap. All rights reserved.