It's just never been clear to me what HATEOAS is really supposed to be good for. Sure, a client can follow the links in an automated fashion, but how is it supposed to know what the resources actually are and which links it needs to follow, which resources it has to create or modify, to actually accomplish anything?
The general idea of returning links to related resources and/or actions is fine and good, but the rhetoric tends to go further, to ecompass claims like the API being "self-documenting" or amenable to a universal client. It always seems to me that this Big Idea of just presupposes the existence of a "smart" client that can really understand the links, one that there doesn't seem to be much sign of.
GitHub's API proudly notes its use of hypermedia and URL Templates in its responses, but I still have go read the documentation to decide what link I need to use, and what needs to fill into those variable slots in the URLs. The template doesn't do much for me that text in the documentation saying "these GET paramters are accepted/required" wouldn't just as well.
> Sure, a client can follow the links in an automated fashion, but how is it supposed to know what the resources actually are and which links it needs to follow, which resources it has to create or modify, to actually accomplish anything?
I've never understood this either. My API client isn't smart enough to follow links and write logic for me, so when they say "the client" can "discover", they must be referring to myself, and not my code? Well I'd much rather read documentation than click hyperlinks inside an API.
This is because the idea is that you would create a new media-type to represent your resource. It is this media-type definition that would determine what rel-types there are and how a client should interpret them.
for example, the spec for the HTML media-type that when a client sees a link with the rel-type "stylesheet" is should fetch the resource using HTTP GET.
As REST requires that media-types be registered the idea would be that we would eventually get a set of media-types that cover things like audio playlists, and how to interact with them.
So any "intelligence" required by a client would be baked into the implementation of the media-type processor. Instead of "client libs" for specific web services, you would have a general media-type parser/processor which could be re-used by clients of different web services to process common media-types.
But apparently individual client libs for each web service that overloads JSON is better.
> But apparently individual client libs for each web service that overloads JSON is better.
I mean, there are so many different types of resources and every API I interact with definitely invent their own. How often do you come across an API offering a playlist of music?
But would anybody really want to use a hypermedia client versus a regular client? Of course not. The regular client can be optimized properly at the level it needs to be to make the user's interaction smoother.
Just guessing, if you had an API for creating documents for example, and you POST a request to /docs/ you'd get back not an just a single ID but a URL to /docs/<ID>. So then the client can operate on that resource and not have to compose it.
It can also be browse-able with a regular browser. If you visit it say with Firefox and go to .../api/ and the browser tells the backend it accepts text/html back, the service would return a list of sub-resources as proper hyperlinks. Pretty formatted json and so on. Might return a few items only not all if there are too many in a collection. After you see all sub-resources .../api/docs/ .../api/widgets/ etc. you navigate by clicking to any of those..
I have actually built that and it seemed like gimmick first but improved developer productivity quite a bit because the API is discoverable and provides live data (instead of just out-dated examples from docs that nobody updates.
Was it HATEOAS? I still don't know. But I knew it worked really nicely. Someone working on the backend could finish a feature and stick it in /api/newfeature/ a front-end developer would browser that get a sense of how it works, what data looks like and so on.
(Bonus points: figured a way to auto-generate docs from code comments from classes and modules and pushed them to the API resources, so now it is was self documented, and had live example and so on).
If you get back an URL to the document instead of the ID, then whenever you need to refer to that document, you need the whole URL. That means that it can't change, which I thought was one of the arguments for using HATEOAS, that you don't need to hardcode the URLs, and can "evolve" the API without breaking clients.
This is a point which is I think overplayed by HATEOAS fans and under-appreciated by HATEOAS haters; using URLs as IDs makes it easier to evolve the API in many cases, but it doesn't make it completely painless to do so.
If you have an object which links to `/users/1/`, and want to change that URL to `/cool_users/1/`, what's the migration path?
Without HATEOAS, you need to update all your clients' code to now generate the new base URL `/cool_users/`. This means you'll need to version your API, so that old clients can continue to access the old-style endpoints in the transition period. (Note that for a business where your customers are making an API integration, this means you're imposing work on your customers).
With HATEOAS, you just need to update the URLs that are returned in your other endpoints. (Generally there is one well-known entry-point into your API, e.g. you return {"user_list": "/users/", ...} with your login token, for example). Now, assuming your clients were using `api.user_list`, that they received, they will without further modification fetch the `/cool_users/` endpoint, without requiring an update.
The one gotcha is if clients are holding on to the IDs of your API objects between API calls; in that case, you will break any code which expects to find those previously-returned members. But note, the worst-case here is that you need to version your APIs, which was the best-case without HATEOAS. In many cases you can get away with such a change without any client-facing changes.
Yes, correct. However, this relies on clients using your API in a hateos way - which they have to go out of their way to do: starting at /, reading the responses, navigating down only using URLs that you return, etc...
No clients bother to do this, in the real world - they just hard code/compose the URLs that they need to use. Why make extra http calls when you don't have to? Why parse all the json-hal (or whatever) to "figure out" which URL to call next, when you don't have to?
Even if most clients did this, you can't enforce it, so not all of them will, so some will still break when you change URLs.
This is why my HATEOAS APIs always return urls in the form "https://mysite.com/{SHA256 hash}", and a façade API looks up the actual path from the cached hash. Hardcode that, bitches.
1) It makes manual testing annoying,
2) I have a nagging feeling that if my users are "doing it wrong" then maybe the API is doing it wrong...
Also I've been playing with autogenerating client implementations using autogenerated swagger specs, and that approach is incompatible with an actual opaque linked API. It would be nice to have the best of both worlds.
Very true - that is the best counterargument. However, we're still back to the worst-case here being the best case without HATEOAS, and well-behaved clients can still reap the benefits even if there are some misbehaving clients requiring multiple versions to be deployed in parallel.
There's a good question about how long you can cache those URLs for as well; it's a non-starter for a client to have to traverse the whole tree from the root for every request. So can I cache the responses for the duration of my auth token, and get a new root node as part of my re-auth?
If you go down that route, now you need to maintain two versions again during migration (but you do keep the ability for 'well-behaved' clients to migrate versions without downtime).
As the sibling comment describes, you _can_ enforce this by obfuscating your URLs, but I've not had the guts to do that yet...
Another approach would be to write great client libraries yourself, so that you know that the clients are consuming the API correctly.
Good point. I think that might be a good thing too in the sense that you could support multiple API versions: by maintaining compatibility for older URL paths. Then clients could also crawl the hierarchy at startup or so to see if what they expect to be there is there or it changed. Maybe respond with a redirect if an older resource is accessed... In practice usually a v1 or v2 is shoved somewhere in the URL or headers.
One of the notions in HATEOAS as far as I can surmise is that we decide to build a common vocabulary describing our API and that becomes the fixed interface for interaction. It's not at all meant to imply that software clients can magically figure out what to do next (thought often a human with a reasonable API app could do so).
Imagine for example that we decided that whenever you request a comment resource from my API then I promise to provide a link in the response called "upvote" which you can follow to upvote that comment.
We have agreed to a fixed interface but left plenty of details flexible.
Maybe we are experiencing heavy load: let's stop sending the "upvote" link - the clients should understand that in its absence the operation is not currently available.
Maybe we have implemented some load-balancing system which redirects clients to `fiji.api-server.com` or `romania.api-server.com` based on their geo-ip data: those clients need only hard-code one top-level API URL into their code and all other URLs come through successive API responses. Load-balancing happens automatically and can even change throughout a single session because the client follows the links instead of building its own URL.
Maybe we are running some test or gradual rollout of a new API or URL structure; as long as we provide those links and references the clients can follow the right path without needing to know about the changes. It's possible that we ended up moving comments from `api-server.com/api/threads/1337/comments/42` to `api-server.com/comments/what-do-you-kow-joe` and this won't break any client designed to follow the interface instead of the incidental details.
For what it's worth I think very few APIs come reasonably close to this design and maybe few even have much need to. REST and HATEOAS become much more important when someone is publishing a public API that many third parties will consume and the ability to introduce non-breaking changes and server-side control of different specifics is important.
HATEOS, and REST in general, is a lot more useful when there are middlemen involved. If I have some link relation type named "api.myservice.com/rels/access-controlled-by" and some content type for authentication policies, then I can build a proxy between my API and clients that looks for links of this relation to resources of this type and automatically implements authentication checks. Instead of writing code to check auth rules in my API, I link to a resource that the auth proxy understands in a common format. This format can evolve over time without breaking the proxy due to content negotiation, and API services written in entirely different languages can still rely on a uniform implementation of authentication rules.
There's all kinds of other directions you can take this including quota enforcement, monitoring, auditing, and other resource-agnostic concerns. More radically, you can make these sorts of proxies reusable services that other people rely on to implement these behaviors. One of the primary motivations for REST in the first place was a standard interface that would allow for insertion of caches at arbitrary points in the Web without breaking everything (in the optimistic case at least). There's even HATEOS in the Cache-Control header, as the cache channels extension uses links to external resources to define the cache channels for resources
To me the best advantage is that by following links, the client doesn't have to builds those links in the first place.
So the client will keep working even if a few months from now you want to change the link to something else. A simple example: if retrieving articles can be done by requesting this link "/articles", I can potentially change it in the future to "/v2/articles" and the client would still work.
The client may not have to build the links but it still has to be aware of their specifics. It has to know that /articles takes arguments X and Y, and that /v2/articles takes arguments Y and Z. You're trading building links to inspecting links. What advantages does this have?
Yep. In theory, one could imagine various schemas at various levels of abstractions that allow the client to 'know' what's going on, automatically deriving it from the schemas.
I think this is also the theoretical promise of one approach to linked data/RDF.
In practice, I don't think it happens, and is not worth the conceptual and engineering overhead for the possibility of something that doesn't seem to be realistic to expect.
(Note: I'm not advocating this approach, I merely repeat what I think I know)
Instead of hard coding the subject hierarchy in templates (like "/topic/subtopic/"), you define a document type that exposes target elements as links (typically with a "rel" attribute identifying the type of element). The client can then navigate the logical hierarchy without requiring this to match the physical URLs (think: federation across departments; instead of having a template "/api/{department_id}/people/{employee_id}", the company can expose a directory document that aggregates links "link rel='employee' target='https://departmentX.company.com/arbitrary/hierarchy/employee... and "link rel='employee' target='https://othercompany.com/api/users/Doe+John'").
The type of linked resources must of course be aligned; there's no magic involved. Instead of the client having out-of-band knowledge about the hierarchy, the client must have out-of-band knowledge about the used document types containing the links.
How? At the end of the day you're still writing an "if" statement branching on a piece of data in the response. In a regular api it might be canEdit, here it's the http verb in an array.
> It's just never been clear to me what HATEOAS is really supposed to be good for.
Have you ever used a web browser? You know how the browser uses media-type to determine how to handle content referenced by a URI? That's what HATEOAS is supposed to be useful for: links identify and locate resources, resource type information tells you what kind of resource it is. The only out-of-band information you should need for an idealized REST API is information on the protocol used (e.g., HTTP) and information on the resource types (media types for HTTP) of the resources used.
(Really, if you want to understand any component of REST, its probably easiest to ask "what is this used to accomplish in the HTTP-based web", since REST is essentially a generalization of an idealized version of the HTTP-based web.)
Yes, but most people aren't making web browsers or similar.
Web browsers are extremely generalized: they display arbitrary HTML, submit arbitrary forms, download arbitrary images, download and run arbitrary JS/CSS.
Most APIs are intended for more specific uses than "all of HTML". Hypermedia is very useful for browsers, but I wouldn't extend that to say, my Imgur clone API.
Indeed you need a client that is capable of mapping allowed methods and links to something useful, but it's not hard to imagine how one might go about this:
* For related resources (imagine a DB FK), instead of listing a uuid you point a link to the location of that resource. This means if the resource location changes the client doesn't need to be updated, it just uses whatever the new link is.
* Methods listed can drive available actions, e.g. imagine you have both mutable and immutable objects, a smart client could reason if PATCH is available then a UI element can be spawned allowing the user to modify this resource.
This is useful in a single client scenario whereby the API schema can be modified to a certain extent without the need to modify the client. It is also useful in a multi-client scenario (either distinct or versioned clients) for ensuring consistency between clients as you no longer need to ensure all clients are modified to use the new schema.
At least, this is how I understood it, I haven't tried it in practice.
Use something like JSON-LD, where the payload contains hyperlinks to documents for each data property.
Your client then can machine read that documentation (which can have long-lived cache headers or be immutable, so your client doesn't spend it's whole time re-checking documentation).
There will be a set of definitions that the client understood when it was coded, and by checking a payload for matching meanings, it can consume the information.
If it finds something it doesn't understand, it could even try to take some compensating action (e.g. suppose it's a client which displays images to it's users, and it encounters a new type of image format. It could perhaps look up a registry of javascript canvas image renderers and download suitable code to display the new image).
The general idea of returning links to related resources and/or actions is fine and good, but the rhetoric tends to go further, to ecompass claims like the API being "self-documenting" or amenable to a universal client. It always seems to me that this Big Idea of just presupposes the existence of a "smart" client that can really understand the links, one that there doesn't seem to be much sign of.
GitHub's API proudly notes its use of hypermedia and URL Templates in its responses, but I still have go read the documentation to decide what link I need to use, and what needs to fill into those variable slots in the URLs. The template doesn't do much for me that text in the documentation saying "these GET paramters are accepted/required" wouldn't just as well.