It's interesting that both of these guidelines kind of reject HATEOAS by mandating explicit versioning. It seems that HATEOAS was never really a thing. It's just too complicated to implement in practice. In that sense, REST in practice has always been just RPC without a clear spec for procedure call like XML or JSON RPC.
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).
FWIW, Roy Fielding's paper[1] on REST (where the word comes from) was very specific that a REST api is a hypermedia/hypertext based api, and that if it isn't hypermedia, it isn't REST. He clarified[2] this later on. This isn't from some internet toughguy, but the guy who literally coined the phrase. Sadly, his implementation was also all XML so gross, but you get the idea. HATEOS is just an implementation of a JSON based hypermedia api.
Me? I'm a fan of grpc for everything. HTTP/2 > HTTP1 and binary compressed protobuffers > deflate compressed (gz) json. http://grpc.io
In short: humans can follow links, even as a website goes through significant changes. Code can follow links, but can't make clear independent decisions when significant changes happen to the API.
Code can follow links as well, as long as the semantics doesn't change.
Aside from versioning through mimetypes, which I believe is a really bad idea, I find HATEOAS to be a beautiful concept, although not very useful in practice.
It's a good place to start though. Trying to design for that can help you shape your API properly, just like SOLID or TDD can do for code.
I should probably have moderated that statement, but mostly I think it's bad because it makes the versioning aspect inaccessible from the lowest common denominator, the browser address bar.
Versioning through the url and versioning through mimetypes requires the same amount of work from the user, but the former is a lot simpler. If you then also take into account, that many http clients have poor support for manipulating headers (SAP and PowerShell (earlier version) for instance), then choosing mimetype versioning makes it harder (impossible) for your users.
> mostly I think it's bad because it makes the versioning aspect inaccessible from the lowest common denominator, the browser address bar.
That also rules out using any HTTP method other than GET. The browser address bar doesn't seem relevant here, and if it were, then it would rule out the vast majority of APIs.
Versioning through the URL breaks interoperability of clients using different protocol versions. If a client using v1 communicates with a client using v2, then they will see two separate sets of resources and they would never have the same identifiers for (what should be) the same resources.
I suspect that we are designing for different clientele.
I'm currently in charge of a pretty classic, GET things out, and POST things in API. If my API supports that some non-IT office guy can get his daily report through the browser, and the API at the same time has the power and flexibility needed for someone with IT skills to do more then I've won. Both me and my users gain by me taking extra steps to make it as accessible as possible.
I also fail to see how versioning through urls break anything, except if the newer client no longer has support for v1. In both versioning schemes you specify which version of a resource you're sending/wants to receive, in one scheme you do it through a header, in the other through the url.
>> If a client using v1 communicates with a client using v2, then they will see two separate sets of resources and they would never have the same identifiers for (what should be) the same resources.
Maybe we're defining versioning differently. I strive to make resource types backwards compatible, but if I can't then it's either a new version or a creating a new more specialized resource type for the specific problem.
> If my API supports that some non-IT office guy can get his daily report through the browser
Most APIs I work with wouldn't work this way even for read-only access because of authentication requirements. I guess you're using something like HTTP digest auth with usernames and passwords?
Even so, that still works for your use case as long as your users are happy with a default version. e.g. requests without a specific version get the latest version, or requests without a specific version get version 1, or requests without a specific version get the latest stable version that changes from time to time. I don't think I've ever come across non-IT office guys that have more complex versioning requirements than that.
> I also fail to see how versioning through urls break anything, except if the newer client no longer has support for v1.
Here's an example:
Client A (using version 2), talking to Client B (using version 1):
Once you break the identity part of URIs, you start getting a tonne of these awkward problems to work around. URIs are meant to be the primary keys of the web, and you're going against the grain of the medium when you break that.
To put it another way, imagine if websites had to change every page URI from /html4/ to /html5/ when they switched versions. That's a whole lot of work and breaking things for no reason. I'd rather follow how the web was designed to work and not create so much extra work.
When you say client do you mean how humans passing around URLs? I can't come up with any reason why two client applications would be sharing full API URLs directly with each other, that seems like very poor design. Even under some sort of weird direct connection situation where the server wouldn't be generating the paths using the URLs as GUIDs seems like a really strange decision.
As for auth a simple token as Github does is enough, and github lets you use it as a header or a get param.
Your choice of HTML 4/HTML 5 for comparison seems aptly poor. Instead of clear versioning, old browsers get HTML they don't understand and fail unexpected, unpredictable and ungratefully ways. The exact opposite of what we should want for API.
> When you say client do you mean how humans passing around URLs?
No, I mean API clients.
> I can't come up with any reason why two client applications would be sharing full API URLs directly with each other, that seems like very poor design.
URIs are the primary key for resources on the web. If two clients want to talk about the same resources, then they need to agree on an identifier, and that's exactly what URIs are designed for.
> As for auth a simple token as Github does is enough
The design criteria in this example is "Can be typed into a browser address bar by a non-technical person". I don't think auth tokens meet that criteria.
> Your choice of HTML 4/HTML 5 for comparison seems aptly poor. Instead of clear versioning, old browsers get HTML they don't understand and fail unexpected, unpredictable and ungratefully ways.
You're conflating two different things there; how versions are communicated, and the compatibility strategy. Whether or not HTML did a good job of maintaining compatibility between different versions, do you agree that changing the URI of every web page on the web to communicate when they change HTML versions is a bad approach?
>> I guess you're using something like HTTP digest auth with usernames and passwords?
Basic auth (over https only) actually, I'm even advocating it. We support expirable tokens as well.
>> requests without a specific version get the latest version, or requests without a specific version get version 1, or requests without a specific version get the latest stable version that changes from time to time
The only way for me to go would be option 2: requests without a specific version get version 1. Any other choice and I'd have customers API integrations breaking, because many of them would not have specified their version mimetypes correctly and would therefore get the default version, which would at some point diverge from their expectations.
>> URIs are meant to be the primary keys of the web, and you're going against the grain of the medium when you break that.
In that context your example makes sense, I just don't see that as realistic problem. In real life URI aren't as static as we pretend. How many companies would make sure the resource urls are still valid after changing the company and domain name?
The real identifier in your case is "foo" and "people" is the type. Accepting that sets you free to do versioning as you please and to save 90% characters in all ids :)
In the end we should design apis for our users, not for our selves. My clients are always non-IT departments, often in enterprisy environments, and if they can get 2-4 hours allocated from IT in the next 6 months to do some API integration, they are lucky. So I design my api for maximum simplicity, and maximum longevity of the using scripts, because if I wanted to make a breaking change, I'd have to wait 6 months before all customers could be expected to have transitioned.
Most of my api users couldn't tell you what a mimetype is btw., but a v2 in the url would make complete sense.
I get where you're coming from, on our latest api version I started out wanting to do full HATEOAS (Bought and read this great book http://www.designinghypermediaapis.com/), but in the end I had too much trouble getting my test customers migrated, so I had to scale back on the desgin goals. Scaling back to what worked for everybody was very informative and the api is now, imo, the better for it.
If I need to bump the vesion it'll be with a vx in the url. An id is a simple non-globally unique thing, that you use to build a url etc.
> Any other choice and I'd have customers API integrations breaking, because many of them would not have specified their version mimetypes correctly and would therefore get the default version, which would at some point diverge from their expectations.
"Some other people might write code that does things incorrectly and then their software might break" is something you can say about any API style, it's nothing special to media type versioning. If many of your customers are writing code that integrates with your API incorrectly, you don't have an API design problem, you have a poor communication problem, and that's going to hurt you in all kinds of ways.
> In real life URI aren't as static as we pretend.
They are static if you don't deliberately break them; I'm just pointing out some of the consequences if you choose to.
> How many companies would make sure the resource urls are still valid after changing the company and domain name?
Companies are laser focused on this if they rebrand. Companies love their search rankings. But how common is it to rebrand? Optimise for the common case, not the uncommon ones.
> The real identifier in your case is "foo" and "people" is the type.
You're turning URIs from an opaque identifier clients can use as-is to a compound structure that clients have to parse, manipulate, and (re)generate. You're putting more logic on the client, and thus giving client developers more rope to hang themselves with immediately after saying they can't be trusted to get things right.
This is not designing for maximum simplicity. Designing for maximum simplicity is to have an API that says "here's a link; follow it", not an API that says "here's an ID, generate a URI from it according to these rules we set out in our documentation, that are different for every type of resource". The latter is more work and more error prone.
I hope it's clear from my previous comments that I don't actually disagree with you on most things, I've just had to make compromises that makes things more accessible to my clientele.
>> "Some other people might write code that does things incorrectly and then their software might break
Welcome to my world :)
>> is something you can say about any API style, it's nothing special to media type versioning
True, but mimetype versioning still makes things more complicated for my average user.
>> If many of your customers are writing code that integrates with your API incorrectly, you don't have an API design problem, you have a poor communication problem, and that's going to hurt you in all kinds of ways.
Sorry, but no. We have full Swagger interface, examples for all major operations in 4 different languages, many pages of documentation and tutorials, and very clear and informative error messages for most situations. Still people will write and say it doesn't work, without having read any docs or even looked at the response.
>> This is not designing for maximum simplicity. Designing for maximum simplicity is to have an API that says "here's a link; follow it", not an API that says "here's an ID, generate a URI from it according to these rules we set out in our documentation, that are different for every type of resource". The latter is more work and more error prone.
I'll reiterate, we're probably designing for different clientele. I'd wager my old Amstrad that most of my users would strip any url'y parts of ids I'd return and just store, what they'd perceive to be, the "true" id in their database, because they only have 20 cloumns allocated in their weird mainframe system, named info1-10 and extra1-10, and the latter only allows 32 chars.
But also consider this: A user uploads a resource through the perfect HATEOAS api, he get's back a big object with a unique url id, and links to all the actions he can perform. It doesn't really makes sense for him to store anything other than the URI though, because I might add more actions later, so he'd have to requery the URI to get links to the latest actions anyway. So for any operation he wants to perform, he should GET URI, parse and follow link with appropriate parameters. Also, in that scenario my system gets hit twice for any operation.
Compare that to: Replace parameters into https://example.com/people/{id}/poke and fire request.
I'd argue that the latter is conceptually simple.
Very soon we'll start on a new api for our ui, and on that there will be no compromise :)
I also prefer putting the version in URL. I find it to be practical and easy way to version your API. I've also been told that it's not the right way to do it.
There's no "right" or "wrong" way tp version APIs. There's only peoples opinions. I'd suggest that if someone is saying you're doing it wrong by putting it in the url, you should probably learn to ignore them as they can't tell the difference between their own preference and facts.
Thank you for your write-up on API design. It's well-written, concise and to the point. I spent quite some time reading it while learning best practices about REST API design last year.
If I get it correctly HATEOS is about the following:
Say you're building a standardised API (with many consumer implementations _and_ many provider implementations all talking to each other). In that case making stuff like the layout of a url or even the protocol over which the data is retrieved part of the specification is putting a needless strain on your API providers. If an API provider uses a shared webhost or a specific framework he might not be able to follow that structure, or it might be harder then necessary.
If instead, your specification only specifies the document layout (Roy Fielding calls it the "media type") then it's easier for people to implement your api. People usually have full control over the document contents.
If you don't design your API like that it's not a REST Api according to Roy Fielding. If your API is not going to be implemented by many providers and many consumers all talking to each other in countless permutations then there's not much use in following all of REST[1]. (And also not much use in calling your API a rest api)
Of course the word REST now means something else then Roy Fielding intended for it. I have no problem with that. I believe he did at some point.
[1] I don't remember the url or the exact quote, but I believe I read a piece by him where he said literally that REST as he described it is only useful for the many2many consumer/provider use case. I might be mistaken.
It even cooler than that: You only have to standardize the types and meanings of the links, the actual implementations can vary in the locations of the endpoints, but the structure of the documents can too, and the client(s) will be smart enough to figure out that on this server they need to retrieve /users/mike/products/, and on that server they need to get /userproducts/mike/all/, just through the fact that the representation of the user (also probably retrieved from different locations) on each server has a link labeled rel="all products" pointing to the right place, regardless of what else was included or omitted from the representation, so you can have interoperability on whatever is standardized even if implementations of both clients and servers have different capabilities they expose.
So, as long as you standardize the semantics and relations, the URL layouts and the document layouts can be quite different, in the same way that you know when looking at a page what the button or link labeled "Home" will do, regardless of the exact location on the page or the fact that on one server it takes you to /, on another to /index.html, and on yet another to /main/home/ .
> In that sense, REST in practice has always been just RPC without a clear spec for procedure call like XML or JSON RPC.
HTTP, even when used without hypermedia, still has interesting features that are not found in traditional RPC. For example, you can’t just stick nginx in front of your gRPC server and tell it to cache stuff. You can’t tell your Thrift client to retry all idempotent requests automatically (without enumerating the functions that are idempotent). Not to mention upcoming things like HTTP/2 push and 103 (Early Hints).
I like to think that HTTP is less of a leaky abstraction over the network.
I think, there is just too much bias involved. At least thats what I am experiencing. Not using HATEOAS ever, but complaining about it makes me angry.
My preaching: If you can write a client by using a semantic document format (e.g. HTML, XML+XSD, Json-Ld), you end up with a more elegant and stable implementation. And as a provider of such an API, I spend more focus on the surfacing domain than the structure of my resources.
As I see it, HATEOAS by providing API structure dynamically depending on application state, restricts the application to non-concurrent use and full backward compatibility, because application state perceived by the client and the actual state might diverge in concurrent use (either concurrent clients or state change by code upgrade). It is impossible to build a client (which does more than query a resource in a loop and simultaneously does the right thing) that will magically know about newly available link if application state changes between getting the resource, making a decision and following a link based on that decision. Which clearly breaks the very intent to make the API self documenting.
Taking Wikipedia example [1], how can a client know whether the application stopped supporting withdrawals altogether (it is dynamic after all) or is it the particular account it is querying? What happens when account is overdrawn between a client querying state and attempting a withdraw? When should a client stop expecting returned endpoint to be still available? How do you document possible outcomes?
Another example of HATEOAS not being thought through is the self link. If a client is querying application through proxy (e.g. forwarded port to directly unreachable destination or some kind of aggregator application), the self link becomes incorrect. Should a client know to magically rewrite the link or treat it as a redirection? If the application is reachable by various paths (domain names, IPs) it must know the path client took, to return correct self link even without a proxy.
HATEOAS restricts application to a set of very specific use cases with subtle traps to fall into. I see no way to avoid both these traps and need for documentation, which, in my view, defeats the purpose. Your mileage may of course vary.