I'm working on a web service API using the HATEOAS REST representation.
My client can create an item (e.g. a stub of a blogpost):
POST /item
204 Created
Content-Type: application/vnd.foo.item+json
{
"id": 42,
"title": "Lorem Ipsum",
"status": "STUB",
"body": "Very long text."
"_links": {
"self": {
"href": "/item/42"
},
"activate": {
"href": "/item/42/activate"
},
}
}
After that the client can activate the item following the activate
link (e.g. go live with the post). So it makes another call to the API:
POST /item/42/activate
200 Ok
Content-Type: application/vnd.foo.item+json
{
"id": 42,
"title": "Lorem Ipsum",
"status": "ACTIVE",
"body": "Very long text."
"_links": {
"self": {
"href": "/item/42"
},
"permalink": {
"href": "/item/42/permalink"
}
}
}
Up to here it is fine. But the problem is that I'm looking for a way to tell the client a suggestion about the next action to do (it's backend business logic).
In my case could be:
I don't have an idea on how I could encapsulate this information in HATEOAS. I was thinking to something like:
POST /item/42/activate
200 Ok
Content-Type: application/json
{
"suggested-action": "check-censure-panel",
"censure-reason": "censored (gambling)",
"_embedded": {
"foo.item": {
"id": 42,
"title": "Lorem Ipsum",
"status": "ACTIVE",
"body": "Very long text."
"_links": {
"self": {
"href": "/item/42"
},
"permalink": {
"href": "/item/42/permalink"
}
}
}
}
But the problem is that every suggested action is heterogeneous for extra attributes, another example may be:
"suggested-action": "go-to-checkout",
"product-order": 424242100,
They don't have a common interface, so I can't make a a vnd.foo.suggestedAction+json
type.
What is the best way to design this response?
The next action is a state transition, and you seem to be using HAL so any state transitions should be presented as HAL.
Clients of your app need to react to what state transitions your app provides. So one very simple thing you could do is send a Location header to the next resource the app should present. You could even 302 redirect them there instead of 200'ing them with the updated resource.
You could provide the next action as a link...and not necessarily a HAL link. You could do it as a Link header (https://www.rfc-editor.org/rfc/rfc5988) but i think that would be weird, i just bring it up to knock home the point that your app needs to tell your client about a link.
You seem to want to use custom media types, but you could use profile links (https://www.rfc-editor.org/rfc/rfc6906) and mix in a profile into your vnd.foo type. You can stick to your vnd.foo type and just have it defined that there is an optional suggested-action link relationship. The problem in your example is you're defining it with data fields, but use a link:
{
"id": 42,
"title": "Lorem Ipsum",
"status": "ACTIVE",
"body": "Very long text."
"_links": {
"self": {
"href": "/item/42"
},
"permalink": {
"href": "/item/42/permalink"
},
"x:suggested-action" : {
"href" : "/path/to/best/action"
}
}
the client can follow that link, present the user with an option to follow that link, or ignore it. In the middle case, it's nice if your app provides some context to the user, like a title field:
"x:suggested-action" : {
"href" : "http://path/to/check/censure/panel",
"title" : "Check Censure Panel"
}
Also you can give a hint as to the resource the app can expect:
"x:suggested-action" : {
"href" : "http://path/to/check/censure/panel",
"title" : "Check Censure Panel",
"type" : "vnd.censure.panel/json"
}
I personally don't like doing that as i like my client to react to whatever i send them, but it's useful when you give multiple suggested actions:
"x:suggested-action" : [
{
"href" : "http://path/to/check/censure/panel",
"title" : "Check Censure Panel",
"type" : "vnd.censure.panel/json"
},
{
"href" : "http://path/to/checkout",
"title" : "Start Checkout",
"type" : "vnd.checkout/json"
}
]
now the app can decide based on well defined media types which of the suggested actions it wants to do, present, or ignore.