Here is the problem with links in a nutshell:<p>> The server is now free to change the format of new URLs at any time without affecting clients (of course, the server must continue to honor all previously-issued URLs).<p>If you have to honor all previously-issued URLs then you aren't <i>changing</i> your format -- you are supporting two formats from now on, the old one and the new one.<p>You can of course tell your users that you will deprecate the old format, but unless you are as powerful as Google your users may prevent you from enforcing a deadline for deprecation.<p>If the URLs in your API responses are FQDNs rather than relative paths, all of this gets significantly harder to deal with.<p>Even if you figured all of that out, links are not idiomatic if your users consume your api via an RPC or GraphQL.
I never had any uncertainty regarding where I can use a given entity id in a well designed API. Fix the naming and organization of your API if that's a problem for your users.<p>Conversely, I often need to log or store API-provided entity ids on my side, and having to parse it out of a URL or store irrelevant URL bytes in my own database would be really annoying.<p>You're not going to avoid the need to compile entity URLs on the client side either, unless you only make requests to entities returned by the API, which would be a weird constraint to design client code around.<p>I really don't see the point to any of this.
I think this takes an overly-simplitisic view of APIs. Going by the primary example in the article, by representing a pet's owner as a link instead of an id, they're basically discounting the idea that there may be separate endpoints that take in an owner id. For example, if there was an endpoint that let you get the invoices by customer, you would still need to understand the templates for that endpoint.<p>More fundamentally, I think it's trying to solve a smaller problem in the face of a much bigger one, you still need to know what the response of any given endpoint is going to be. Just because they've passed me a link, doesn't me I don't need documentation on what endpoint that link points to. I still need to know that the owner is a link to the people endpoint so I can properly parse that result. That in turn requires just as much documentation (IMO) to describe the relationships as it would to properly document your URI templates.<p>Obviously, the primary reason to use links over ids is to give the developers of the API more control over changing things like routes and Ids and whatnot, but I feel like it is a bit disingenuous to make it out to be a much better user experience or something, since it really isn't.
There's literally not a single upside of this shown in the article.<p>> The server is now free to change the format of new URLs at any time without affecting clients (of course, the server must continue to honour all previously-issued URLs).<p>No more than it was previously.<p>> The URL passed out to the client by the server will have to include the primary key of the entity in a database plus some routing information, but because the client just echoes the URL back to the server and the client is never required to parse the URL, clients do not have to know the format of the URL.<p>Instead, you now have to require new kind of knowledge, one of the keys which must be present in schema and their meaning. E.g. knowing that "pets" key is present and leads to a relationship of a particular kind, with all the implicit logic added and documented. And what if you want to get pet's owners with some additional parameter, like only getting ones which are exclusively yours? Would you need to edit that "pets" url adding "&exclusively_owned=true"?
I don’t mind using a link, but I’d prefer to have both the exact id and the link to avoid having to parse a link in an unreliable way to get the actual id.<p>It’s interesting how GraphQL changes the base point of the article concerning documentation/ease of api use.<p>In our GraphQL resolvers we typically add two fields, thing_id which resolves to the id string, and thing which resolves an object that you can drill into as desired.<p>I was starting to see a lot of GraphQL APIs only add “thing”, which meant you had to do a lot of queries like below just to get the id.<p><pre><code> thing { id }</code></pre>
Why did the author of this blog post decided to pass web links in resources and completely ignored standard practices such as RFC 8288 which employs the Link HTTP header?<p><a href="https://tools.ietf.org/html/rfc8288" rel="nofollow">https://tools.ietf.org/html/rfc8288</a><p>Additionally, compact URIs (CURIES) are also widely used in this context.<p><a href="https://www.w3.org/TR/2010/NOTE-curie-20101216/" rel="nofollow">https://www.w3.org/TR/2010/NOTE-curie-20101216/</a><p>I feel that the author tried to reinvent HATEOAS but skipped a cursory bibliographical review and jumped right into reinventing the wheel, and one which has already been reinvented multiple times (HAL, JSON-LD, etc...)
A thing may be identified by a URI (/person/123) for which there are zero or more URL routes (/person/123, /v1/person/123). Each additional route complicates caching; redirects are cheap for the server but slower for clients.<p>JSONLD does define a standard way to indicate that a value is a link: @id (which can be specified in a/an @context)
<a href="https://www.w3.org/TR/json-ld11/" rel="nofollow">https://www.w3.org/TR/json-ld11/</a><p>One additional downside to storing URIs instead of bare references is that it's more complicated to validate a URI template than a simple regex like \d+ or [abcdef\=\d+]+
The idea of using uris instead of keys is not a new one (as has been mentioned by other commenters). Every few years the idea gets a resurgence of people who say that REST apis should be HATEOS and that <i>we are doing it wrong</i>.<p>It seems obvious that the cost-value for this is simply not there, if it was good enough, you'd see developers requesting it and many more vendors implementing it. So far I haven't seen any recent changes that might skew the cost-value towards the uri's favor, only the opposite (cue GraphQl).<p>Using uris have little benefits, but it does have the following problems:<p>As a user of the api:<p>* You need to keep an arbitrary length key in your database if you save references. It can cause some issues with certain setups (less so these days though).<p>* If you keep the entire URI as identity, then you can't use multiple endpoints. For instance lots of companies have an endpoint for production and one for reports - using URI for one endpoint in another is quite awkward.<p>* Working with queries is troublesome, especially with get request. Consider searching for all transaction of a specific account, where the account's identifier is `<a href="https://api.google.com/v1/account/123`" rel="nofollow">https://api.google.com/v1/account/123`</a><p>* Upgrading to a new version of the api (one with a different url like v1/v2) now not only requires you to change your code to work with the new version, but also migrate all previous ids you kept in your database, which is a much different and more error-prone issue then simply changing code.
The article knowledge has been lost to time. In "relational databases" you always name the foreign key as the relationship between tables.<p>From "A Practical Guide to Relational Database Design" from the year 2000.
"Each relationship line should carry a pair of descriptive elements, which define the nature of the association between entities. A name is a single word or descriptive phrase; it should always contain a verb such as: owns, owned by, holds, administered by, etc. Examples from our simple model are: A PART is sold on an ORDER LINE. An ORDER LINE is placed for a PART."<p>But, this has been lost because the practicality is that it is hard to know what is the element. As other comments points.<p>Probably the best is both worlds: PersonId_Owner. PersonId_Veterinary. Or something similar.<p>It seems that such a discussion should have been solved decades ago. And here we are. :)
I feel like the author has never used graphql. We're also just finally graduating past REST to something more meaningful. This advice feels 15 years late and now totally wrong. An API shouldn't be tied to a protocol like http, it should be able to move on to other things.<p>Ahh I was correct:<p>> I have never used GraphQL, so I can't endorse it, but you may want to evaluate it as an alternative to designing and implementing your own API query capability.<p>You really shouldn't write this giant article without having tried that.
Hypermedia As The Engine Of Application State (HATEOAS)<p><a href="https://en.wikipedia.org/wiki/HATEOAS" rel="nofollow">https://en.wikipedia.org/wiki/HATEOAS</a><p>The idea has been around for a while; I personally don't think it is a good idea.<p>There is even a content type (or two) for it: application/hal+json and application/hal+xml.<p><a href="http://stateless.co/hal_specification.html" rel="nofollow">http://stateless.co/hal_specification.html</a>
I just don't understand why mixing two different concepts at two different abstraction levels only for some, apparent, simplicity.<p>On one level we got ID unique identifier of a resource, on another level we have URL, how to get a specific resources.<p>They are just different things that shouldn't be mixed.<p>What if tomorrow I want to get the same resource via graphql? Or in a message bus?
What is the source of knowledge for the client about fields, where they can read link to the entity?<p>For human it's obvious that dog has an owner, so field "owner" should be used, but for code - you need to write it, "document it".
So if you're going to "document" every field containing link to external resource, you'll end up with even more code, than just "documenting" API endpoints.<p>Also, pretty often you need multiple IDs of entities to send POST/PUT request - just to create a relation.<p>POST /adoption, owner_id=5, dog_id=7.<p>How should it look with links? Will it be issue for the server to parse them? And it's just simple case with 1 to 1 relation, sometimes you need to add sets of objects to another entity.<p>It's a really bad advice and after reading this I'm not sure I should trust other articles from that source.
The document also forgets that API's are not read-only. So let's say you have users and usergroups and you can request a usergroup with its list of users and you can add users to usergroups.<p>If you use links for read, you should also use them for writes, otherwise it's quite inconsistent. So now you need to add a lot of parsing everywhere to extract the id's out of the urls, just for the sake of being more dogmatic
I've literally spent 2 years working on a project that did exactly what this article is recommending. There were some places which needed the relative-url as an identifier, and other places which needed the "database id" as the identifier. We constantly had to extract the id from the URL, or convert the id into a URL, and keep a mental map of which format each input was using, and which format was needed for each output. It was a mess. I would personally not recommended this at all.
Basically, advocating for dynamic typing rather than static typing, across an API boundary. You’ll save code constructing API requests but need to create a lot of application logic to handle an owner link and pet link separately, since they have different semantics.
No.<p>What's the point? None of this is useful to me, all of this is extra complexity. Why would I want to expose every addressable entity through URLs and HTTP? That's not what IDs are for.<p>I'm aware that this fits into the whole REST idea. I still don't care.
I am surprised the article didn't mention RDF. In every data facet of RDF the data is uniquely identified by URI. In the case of RDF the URI is merely a unique identifier that can resolve to a HTTP resource, but doesn't have to.
The fact that JSON is just a format standard and doesn't have specified components (like links etc) but instead we have to built those on top has cost us a lot in APIs. Btw, according to RFC 8288 Web Linking (and before that 5988), a link consists of 3 parts + 1 optional part:<p>"In this specification, a link is a typed connection between two
resources and is comprised of:<p><pre><code> o a link context,
o a link relation type (Section 2.1),
o a link target, and
o optionally, target attributes (Section 2.2).
A link can be viewed as a statement of the form "link context has a
link relation type resource at link target, which has target
attributes".
For example, "https://www.example.com/" has a "canonical" resource at
"https://example.com", which has a "type" of "text/html".</code></pre>
"<p>That's why you need a standardized link component that is globally accepted/understood that takes into account all parts of the linking, instead of having various ways depending on the API/JSON-based Media Type to communicate that something is a link.
I used links but I'm gonna rewrite this code to simply pass IDs. The reason is simple: I need additional configuration for my server to know its hostname and I don't want to do that. May be my server even have few different hostnames for different clients? So I must parse client request and extract Hostname? But it's served via reverse-proxy, so I must do some complex configurations to pass this information. So many issues. But client knows perfectly well which server he's talking to, so he can just append server-base and id. Yes, client must know about its structure, but it's nonsense that client can somehow learn something. I'll code that anyway.<p>May be it makes sense when you're writing an API and some different person writes a client and she's so shy that she don't want to even ask you. Yeah, she can inspect answer and find out that this seems like a link to query further. I never was in that situation, I was always building all software myself, so for me this does not make sense.
I'm not sure about the verdict on URL versioning. I've used header versioning extensively and while flexible, it also carries some big downsides, mainly that it's confusing for new developers, and makes it real hard to casually explore the API in a browser (bad DX). I'm also not sure you do want to encourage mixing v1 and v2 API representations; I have certainly seen cases where it makes progressive upgrade easier, but it can also bring inconsistencies, so having a default new integrator path of "start at v2/login and use whatever links you get" is appealing.<p>I do like the idea from Stripe of having Accept header versioning, but pinning every new client to default to the newest GA version. Gets around most of the DX concerns I raised, but it's a bit more machinery to wire up.
One advantage I see is that now you can do<p><pre><code> /pets?owner=/people/98765
or
/pets?owner=/org/98765
</code></pre>
which makes your api more polymorphic.<p>Having said that I don't think URL is the right term to describe this. It's more like a <type, id> tuple.
I wasn't fully convinced by this article. Language specific API wrapper clients can abstract all these complexities. Having links for IDs felt very unnatural but I guess that's because I have never came across an API that uses links like the article suggested
Conceptual and data modeling aspects of this problem are discussed in [1]. It compares links with joins (and foreign keys) by proposing a solution (concept-oriented model) which does not use joins at all but rather relies on links only.<p>Essentially, a foreign key is viewed as a relational workaround for representing links with some significant drawbacks and the question is why not to use links directly without relational wrapping.<p>[1] Joins vs. Links or Relational Join Considered Harmful: <a href="https://www.researchgate.net/publication/301764816_Joins_vs_Links_or_Relational_Join_Considered_Harmful" rel="nofollow">https://www.researchgate.net/publication/301764816_Joins_vs_...</a>
Sometimes I wish REStful/REST the whole idea was a lot more opinionated. Sure you can have opinionated frameworks but nothing is stopping you from using a patch like I would a delete... (not the best example but you get the gist).
I think the issue brought up in this blog post pales in comparison to the two biggest problems faced when working with REST APIs: querying for nested data, and the limitations of CRUD interfaces to model complex behavior.
Why on earth would you blow out your request size for the sake of purity? Calling GET /pets is going to return a lot of instances of pet with very similar URLs.
JSONAPI Specification also makes use of URLs in links to resources:<p><a href="https://jsonapi.org" rel="nofollow">https://jsonapi.org</a>
What really convinced me about HATEOAS and links was the first time I used a HAL browser and started clicking around to discover an API using only its entry point and navigating from there.<p>From that point on I try to use it as much as possible. A typical API response for my projects looks like this:<p><pre><code> {
"id": "3ccf0f1b-dd3f-48d9-911a-ddf479078c37",
"name": "Quantus Tasks",
"description": "Quantus Tasks Desktop Application",
"license_key_type": "alphanumeric_32",
"created_at": "2019-05-12T10:45:42.089406Z",
"updated_at": "2019-05-12T10:45:42.089406Z",
"_links": {
"self": {
"href": "http://localhost:8000/v1/applications/3ccf0f1b-dd3f-48d9-911a-ddf479078c37"
},
"licenses": {
"href": "http://localhost:8000/v1/applications/3ccf0f1b-dd3f-48d9-911a-ddf479078c37/licenses"
},
"templates": {
"href": "http://localhost:8000/v1/applications/3ccf0f1b-dd3f-48d9-911a-ddf479078c37/templates"
},
"apikeys": {
"href": "http://localhost:8000/v1/applications/3ccf0f1b-dd3f-48d9-911a-ddf479078c37/apikeys"
}
}
}
</code></pre>
It still has the ID field in there for cases where the client needs to store the id itself but it should not be used to template URI's for related resources, the links are there for that.
I think I am missing the core concept here. This still uses IDs, only now you have to grep them out of a URL construct instead of just getting them directly?<p>I don't get the intent at all here, but I have a suspicion whatever problem this tries to solve is better solved by UUIDs or by doing nothing out of the ordinary.
Reminds me of HATEOAS
see here:
<a href="https://en.wikipedia.org/wiki/HATEOAS" rel="nofollow">https://en.wikipedia.org/wiki/HATEOAS</a><p>Also RDF endpoints usually use resolvable URI's to connect concepts and objects with each other.
No one thinks twice about using links for images. You wouldn't make an API that specified images as "image id 2345, which the client can find at /images/{id}"
Why on earth would one trade off a short, static unique identifier for a potentially long, dynamic "link" that essentially binds all data to some crappy API that will be outdated in a few years? Is it really _that_ hard to use keys?
In micro services world this makes perfect sense. In the legacy land - this is slightly tricky as dependent application (where our foreign key points to) may or may not be in services world. But I get the idea.