And if you have one hard-coded URL... and use it as the root of other URLs, then it's no hard to support than anything.
URLs are useful in REST because the API often crosses domain boundaries and company boundaries. One site links to another and so on. Most APIs are not like that.
In fact they're only like that when they serve, say, static image assets off a CDN, and guess what, then passing back a URL in your API is self-evident and natural.
But not for everything IMHO.
If your API only needs a single domain name (as over 99% of the APIs are), clients can just have the base URL encoded in a constant or a configuration file.
What HATEOAS ostensibly gives you is not the ability to painlessly switch domains, but rather to change your entire URL path structure and even the entire schema represented by your REST endpoints.
This feature turns out to be quite useless for most REST clients. Cosmetic changes to URL structure aside, any useful change (in other words - a schema change), will break the client logic, which is still hard-coded to deal with a predefined schema, predefined types of resources and a predefined set of actions that can be performed on each of them.
Roy Fielding accurately notes that a truly "RESTful" app should let the engine of its state be completely driven by the server. In other words the client cannot have any _specific_ logic related to "state transfers".
If your client is not doing this, like a browser does, than what does it have left to gain from implementing HATEOAS? I'm honestly interested in hearing good arguments, since I haven't heard anything that managed to convince me yet.
I've always worked all my API up to level 2 (HTTP Verbs) of the Richardson Maturity model, but I've never felt convinced that HATEOAS is worth the effort. It seems like the majority of HTTP API designers out there have the same opinion.
Which would be utterly pointless, because the fact this schema is opaque to the clients means that it's meaningless to the clients.
So you have no reason to change it.
> I've always worked all my API up to level 2 (HTTP Verbs) of the Richardson Maturity model, but I've never felt convinced that HATEOAS is worth the effort. It seems like the majority of HTTP API designers out there have the same opinion.
The "maturity model" is honestly non-sense and Fielding also thinks that.
It's kinda like first level to being a bird is flapping your hands, second step is whistling with your mouth... No, you're not any closer to being a bird, you need to be the entire thing for the benefits of being a bird to come about.
Trying to stick to the CRUD concept of HTTP verbs is rather harmful. CRUD is a common starting point for a domain's verb structure, but being restricted to it is crippling and results in bizarre obscure semantics or underdeveloped business constraints.
I'd say to hell with all of it. I stick to HTTP as-is, and that's it. Also it's telling I think that to this day HTML forms only support GET and POST. Even HTML isn't RESTful I guess. /s
What you get by implementing what is called a "RESTful API" nowadays is something that is very different from the original concept of REST, but is still useful to some applications, over plain RPC.
Let's unpack it. Fielding RESTful system as a system which implement the following constraints:
1. Follows a Client-Server model
2. Is Stateless (request-response model)
3. Indicates cacheability of responses
4. Has Uniform Interface defined by the following restrictions:
Identification of resources (URIs); Manipulation of resources through representations (media types); Self-descriptive messages; Hypermedia as the engine of application state (HATEOAS)
5. Isolates access to non-adjacent layers
6. (Optional) Supports code on demand.
Applying all the restrictions stated above, lets you immense benefits: for instance cacheability, stateless and uniform resource identification make your system easy to distribute and scale to handle large amount of traffic; self-describing messages, manipulation of resources through representations and code-on-demand let you have a system that can evolve gracefully without requiring a big bang protocol upgrade.
The half-assed "RESTful APIs" of today implement only a subset of the constraints listed above. They are always client-serves, stateless, isolate access to non-adjacent layers and implement two of the uniform interface restrictions (URIs and manipulation of resources through representations). Sometimes they also indicate cacheability. However, they do not support HATEOAS (including most of the level 3 wannabes), code-on-demand and most importantly self-describing messages.
The lack of the constraints above means our modern APIs cannot achieve some of the benefits the web browser does: most notably flexibility and open-ended evolution. But they do have some other benefits over traditional RPC systems.
I think the maturity model itself is quite useless, but the following properties are useful:
1. Correct HTTP verbs: Differentiating between PUT and POST will let your intermediaries know when the request can be retried. 2. URIs uniquely identify resources: beyond the obvious benefit (cachability), it allows for easy sharding, distribution and smart redirection by any intermediary server.
With traditional HTTP-RPC (even with a standardized protocol like gRPC or SOAP), your intermediary has to be fully aware of the RPC schema to cache or know when it can retry. If you want to add a sharding gateway you need to especially develop a customized solution. A Level 2 "RESTful API" still has all these benefits.
Suppose that you wanted or needed any or all of these things: REST doesn't give these things to you for free. If REST was a standard that people actually followed, as opposed to an almost meaningless buzzword, maybe you'd get some of these things for free. The original REST paper doesn't specify any of these things to a degree where they would "just work". If it did, it would be a standard so complex, nobody would implement it properly.
> With traditional HTTP-RPC (even with a standardized protocol like gRPC or SOAP), your intermediary has to be fully aware of the RPC schema to cache or know when it can retry. If you want to add a sharding gateway you need to especially develop a customized solution. A Level 2 "RESTful API" still has all these benefits.
The only thing you're reasonably going be able to cache through a "dumb" intermediary are GETs for static or rarely changing resources. You don't have to handle these through RPC, you can use plain HTTP handlers for that, you don't need to go "full REST".
Fielding never claimed this architectural style works for machine apis. And it doesn’t.
> It is certainly simpler than having to extract the URLs from some other request.
It's not extracted from other request, it's extracted from the response to the original request, and using a field from the local struct is not harder than using a global constant.
Well they shouldn't! And if the API arbitrarily changes URLs around without reason, then surely they might also change the URL for the initial request.
If you treat them as public interface, they shouldn't, yes. But if you treat them as internal implementation details, they absolutely could.
One can also think about it as static vs dynamic dispatch: you make an initial request, you get a vtable of "method name -> method pointer" as a part of response. Or you can hardcode the method pointers and make it the remote server's job to properly chose the appropriate internal endpoint.
If they have “all the info they need”, they have the identity of Y, which in a system designed around REST is the URL, and if Y is a resource, then its URL is the URL for actions on it, and the type of thing it is will tell you what method to use and (where one is applicable) resource to send to accomplish the action.
HATEOAS is more than just shipping URLs.
I tend to like APIs with small surface areas because it means I don't have to drag along outdated endpoints across minor versions because the org doesn't want to v2 anything. URLs as an implementation detail makes backwards compatibility much easier. So many times it's been useful to combine or split endpoints as the business needs change without affecting clients.
The worst part about using HATEOAS for me has been clients who are used to constructing URLs continue to do so and create a ticket where the obvious first question is "Are you constructing that URL?"
So this gives you only the freedom to change the URL naming scheme, but if you want to change the functionality in any way that's not 100% compatible, you still have to drag along outdated endpoints with the old functionality or you'll break clients.
But this goes so much in contrast to your comment that it seems that I'm probably misunderstanding you - can you give an illustrative example on how exactly does this allow you to combine or split endpoints without affecting client software that sends/takes data from those endpoints and does some action (business logic) based on that data?
[1] https://blog.ploeh.dk/2013/05/01/rest-lesson-learned-avoid-h...
> In the future, I plan to make URLs opaque when building level 3 APIs. Instead of http://foo.ploeh.dk/customers/1234/orders, I'm going to make it http://foo.ploeh.dk/DC884298C70C41798ABE9052DC69CAEE .... Obviously, that means that my API will have to maintain some sort of two-way lookup table that can map DC884298C70C41798ABE9052DC69CAEE to a request for customer 1234's orders
I pity the people who had to work with him, and the many thousands of hours this has probably ended up costing.
Strictly speaking, you're wrong: a URL specifies a location of the resource, and a mechanism for retrieving it. If the location of the resource changes, so would a URL.
Even without this philosophical argument, there is an actual practical issue that URLs do change all the time. Both HATEOAS and Persistent URLs are mechanisms to cope with that: HATEOAS proposes to write clients that instead of using hardcoded URLs would use late-bound URLs supplied by the origin server; PURL proposes to hide the origin server behind a proxy that would translate hardcoded URLs used by the clients to the current scheme and either proxy or redirect the requests.
The main problem with either solution is that URLs use domain names as their "authority" part, so whenever you lose control of the old domain, the "entry point" URL has to change and there is nothing much you can do about it.
Yes, I know that URLs (URNs, actually, IIRC) were supposed to be immutable and eternal, with redirects set up (and maintained somehow, by someone, forever) to point to the current locations, but that didn't work out for obvious reasons.
I still don't see any reasonable scenario where an API would change the (site-relative) URL's without also changing the semantics of the API. And if the semantics change you would have to rewrite the client anyway.
In any case, if you change the URL's without changing the semantics you should respond 301 on the old URL, so even a client with hardcoded URL's would work correctly without the need for HATEOAS.
You force your users through garbage you get support tickets like that. Instead of blaming the users, think why your great approach is actually problematic for them.
The single entry point has no benefits unless the api is multi-company, no one specifically hosts it. Is this the case? If no, your hateoas and single url approach is pure cargo cult.