HATEOAS and links/actions
Asked Answered
P

1

1

I'm trying to wrap my head around how to (and if to) implement HATEOAS in my api. I like one of the concept of feeding the client only actions that are appropriate in the current situation. However I'm not sure if I'm implementing this idea correctly.

Let's say I have a resource type order with a status that can be changed, it can have different statuses (processing, accepted, declined, expired, successful). Should I then create the following json object:

{
    ...
    "links": {
        "accept": "http://example.com/order/1/accept",
        "decline": "http://example.com/order/1/decline"
    }
}

Or am I creating unnecessary actions here? And if above is correct, should the status be changed by a PATCH, or GET? And if it were PATCH, how would one know (defeating the purpose of hypermedia)?

Edit: Forgot to add order id

Prolusion answered 12/9, 2016 at 19:40 Comment(0)
L
7

Let's say I have a resource type order with a status that can be changed, it can have different statuses (processing, accepted, declined, expired, successful).

Warning: unless your domain happens to be document management, you may be getting yourself into a tangle if you try to match your resources with your domain concepts. Jim Webber warned about this years ago; in most cases, your resources are part of the integration domain. They are little digital pieces of paper that are used to interact with your domain model.

{
    ...
    "links": {
        "accept": "http://example.com/order/accept",
        "decline": "http://example.com/order/decline"
    }
}

The basic idea here is fine - if the client wants to invoke the accept protocol, they know which link to use; likewise for the decline protocol. Anything else, the client knows it shouldn't be trying to do because the links are not available; for instance, if the goal of the client was to expire the order, it would know that it had run into a dead end.

The spelling of the URI here is a bit strange; the client shouldn't need to care about the spelling, but the request should be stateless. If order is a type of resource, then how are you communicating which order to accept/decline.

And if above is correct, should the status be changed by a PATCH, or GET?

Neither.

GET is wrong, because advertising that a resource supports GET is a claim that the operation is safe, which means that intermediary components can speculatively follow the link, pre-loading the result to save the client time. Not what you want to be doing if you are understanding the message to communicate a decision made by the client

PATCH has two issues. First, it's designed for document manipulation; if your application is a document database, then PATCH is fantastic for making one or several scoped changes. But it's not so great for dealing with proxy representations of the business model; instead of the client communicating its intent to the server, the client communicates the side effect that its intent would have on the representation, and then the server tries to deduce the intent from the side effect.

But you could potentially get around that; after all, you can choose the media types your support, and could potentially limit yourself to those types which allow you to express the client's intent.

The second issue is that PATCH is not idempotent; it has the same failure modes as a POST -- if the request isn't acknowledged by the server, the client's cannot readily determine if it is safe to retry the request.

The straight forward approach is to think of the edits as analogous to putting a hand written note into somebody's inbox

Order 54321 should be accepted. Please get it done. Signed, the client.

In other words, we aren't trying to manipulate the order resource directly, but instead delivering a note to an inbox that will have the side effect of manipulating the order. The client describes the change it wants, the server makes the change (or not, if you are allowing the server to have autonomy).

For this approach, PUT (which is idempotent) or POST (which is not) are appropriate. In effect, you are adding a new message to the inbox collection, and can use that idiom to choose the suitable method.

And if it were PATCH, how would one know (defeating the purpose of hypermedia)?

How would the client know to look for links in the "links" property?

How does the browser know what to do with the links in an HTML document?

The REST answer is: they know because you've invested a bunch of effort in designing the media type itself. In the case of the web, a bunch of time and effort was invested in designing the text/html media type, and so any client that is designed with html in mind can consume the representation produced by a server that shares that understanding -- the client and server are decoupled from each other, but share a common ground.

In the case of HTML, for the most part the media type defines the HTTP methods associated with the links (the exception being forms, which allows the representation to specify a method from a restricted set). Stand on the shoulders of giants.

Lipoid answered 12/9, 2016 at 23:3 Comment(4)
Thanks for your thorough response (I forgot to add the resource ID you're right). So from reading above you would choose PATCH. What would be the json payload of the PATCH? Something like: PATCH: example.com/order/1/accept {status: "accept"} ?Prolusion
No, I would PUT a new resource. If you must PATCH, then you should choose a PATCH media type. Perhaps: tools.ietf.org/html/rfc6902Lipoid
Ah ok, sorry to have misread you. So PUT: example.com/order/1/accept with a payload {status: "accept"}. Or would you recommend a different payload?Prolusion
My payloads look like message written in the language of the domain; "OrderAccepted" with orderId and other data of interest.Lipoid

© 2022 - 2024 — McMap. All rights reserved.