Roger Johansson
Roger Johansson

Reputation: 23214

Finding a restful resource when using HATEOAS?

When reading about HATEOAS/Hypermedia constraint, one thing that I often see is that a resource should have a self/href of some kind. The argument for this is that the client should not need to know how to construct the URL for that specific resource, e.g. for updating the resource.

e.g.

{
     //instead of "id":"123"
     "href":"/api/v1/orders/123",

     order state...
}

I love that idea.

But how does that concept fit with fetching data? Let's say I need to fetch an order with a specific order id, how would the client deal with that? I still need to know how to construct the URL for the resource in that case, right?

How is the client supposed to know where and how to look for a resource? It has to know about the API URL's in some way or another?

Upvotes: 6

Views: 1725

Answers (4)

Antony gonzalves
Antony gonzalves

Reputation: 124

Let us take your situation. They way i would look at it. Your search always returns a interaction (i.e URL). Then the client can always start navigating from there to get to the data.

The key is to not to return the value but only return the representation.

We are using a platform for this implementation. It has a unique interaction based language. The platform as it is completely HATEOAS compliant, returns URL, when a search is send to it.

In your example if we send a booking id. It returns all order url which has this booking id . The client then can navigate thru these urls.

I hope this helps with your query.

Upvotes: 1

Daniel Terhorst-North
Daniel Terhorst-North

Reputation: 401

A well-designed HATEOAS API will have clear entry points, and the rest of the application behaviour will be discoverable from there.

Since the question is about HATEOAS specifically I would say using a URI template puts too much responsibility onto the client. Instead you should provide an explicit URL for each valid action on the resource given the current application state.

This isn't just a stylistic point. If the server provides a template then the client developer has to write code to populate the template, which creates a coupling between them. You can't change the server-side URL structure now without breaking the contract with its clients. With HATEOAS you associate a URL with each action allowed on a resource, and the client just cares about the action. The URL is effectively an opaque handle: "Choose your own adventure", as Ian Robinson says.

HATEOAS is about using hypermedia—media containing links to other media—to enable a client to navigate around an application with no other knowledge than the last response it received. That means each response should provide the client with ready-to-use URLs representing all valid actions on the current resource.

Remember, the thing-you-get-over-the-wire is only a representation of a resource (REST stands for REpresentational State Transfer). There can be different representations of the same resource based on your current context, say your current set of permissions, and the current application state. Different representations can legitimately offer different next actions.

Using your example, the owner of an order might see this:

{
  "id": "/api/v1/orders/123", // reference to the current resource
  "rel": {
    "cancel": {
      "url": "/api/v1/orders/cancel?order_id=123",
      "method": "POST",
      // Metadata about what the cancel operation returns...
    },
    "list_orders": {
      "url": "/api/v1/orders",
      "method": "GET",
      // Metadata about what the list_orders operation returns...
    },
    // ...
    // Other operations available to the owner
  },
  // ...
  // Order state
}

Here I'm defining a map that uses the key as the operation name, or relation in HATEOAS terminology, although I could equally have a list of maps with a key called "rel" and values of "cancel" and "list_orders" respectively.

Another role, say the shipping coordinator, may not see the cancel operation because they den't have permission to cancel an order.

Upvotes: 6

Mark Seemann
Mark Seemann

Reputation: 233160

While you can supply an URI Template, I always consider that a bit of a smell when I have to do it. Sometimes it's necessary, but if a major part of my API ends up using that approach, I wouldn't call it HATEOAS or a level 3 RESTful API.

How to model the scenario is much dependent on the context, and that's probably the reason why Pete asks about the use case.

Often, you can model a RESTful API in the same way as you'd model a human-usable web site. For example, you could have a resource that lists all of a user's orders, sorted e.g. with the most recent orders first:

{
    "orders": [
        {
            "date": "2015-01-25",
            "total": 1234,
            "links": [
                {
                    "rel": "order",
                    "href": "https://follow.the.link"
                }
            ]
        },
        {
            "date": "2015-01-22",
            "total": 1337,
            "links": [
                {
                    "rel": "order",
                    "href": "https://follow.this.other.link"
                }
            ]
        }
    ]
}

I client can look for the order it needs in this list, and then, once it's identified the interesting order, can follow the corresponding link that has an "order" relationship type.

As a general resource, the RESTful Web Services Cookbook is a indispensable for answering these sorts of questions.

Upvotes: 5

Stefan Tilkov
Stefan Tilkov

Reputation: 1693

You do it using exactly the same concept you’d use in any web app: By sending the client a recipe for building the next request. In HTML, you’d use an HTML form to do that. If you’re using JSON, your format would need to have the same concept, e.g. using a URI template, or even a form, i.e. a list of key-value pairs, possibly with some of the values already filled in.

As an example, a previous request might return something like this:

{ "order": {
    "link": {
        "template": "https://your-api.com/orders/{id}",
        "method": "GET",
        "type": "application/json"
    }
  }
}

Of course your code would still be dependent on some information – in this case, the fact that the id is called "id" in the template. But by adding this indirection, it doesn’t know about the actual URI. Also note that I’ve added method and type parameters as an example; whether you choose to make that configurable depends on the format you use. For a more elaborate example, see (or use) the excellent collection+json format.

Upvotes: 5

Related Questions