Question about HATEOAS shortcuts
Opened this issue · 15 comments
In some cases we have to save somehow the actual client state, which is bound to specific REST resources. E.g. we selected a product from the catalog and add to the user favourites, or we use an SPA REST client and we want to link it with https://example.com/our/client/catalog/product/123
. In those cases the client needs to know which links to follow to reach the a certain client state again, e.g. main -> catalog -> next-page : until-we-reach-product-with-id(123) -> product(123)
.
We have 2 problems here:
- Using pagination to find the link to the product having the right id takes forever and uses way too many resources, so we need some sort of shortcut:
main -> catalog -> find-product-by-id(123) -> product(123)
. Normally we would not have that shortcut. Ofc. we can have search, but a typical user does not search by product id, but by criterias likecolor:red, size:medium
, etc. So we need extra link(s) in our REST API just to support saving certain client states by collections. - The client needs to know about some sort of REST sitemap in order to reach the specific client states in these cases. If we want to build our client on top of a REST browser it would be a good feature to support this with some sort of sitemap format the REST API could provide. So the client would not need to know that it should follow
main -> catalog -> find-product-by-id(123)
to get to theproduct(123)
link, because it could get that info from the sitemap. All it needs to know that it needs to find and follow theproduct(123)
link to get to the client state it needs to reach. We could even use a REST browser and just pass the operation we want to use and the input and it could discover the API and find the proper link on its own.
I am curious if somebody is working on a recommended solution for REST sitemaps in this topic, or if you have a better idea for these cases.
but a typical user does not search by product id
Indeed. I find this statement contradict the actual example above which assumes you are looking for product-with-id(123)
. If that is the desired outcome then maybe a search by the id is what your API needs?
So we need extra link(s) in our REST API just to support saving certain client states by collections.
Could you elaborate? I don't see how the collection would save client state? Seems to violate statelessness of REST.
Regarding the actual problem you're trying to solve,
In those cases the client needs to know which links to follow to reach the a certain client state again
I think you're assuming that the client always has to start from the entrypoint. Thinking in terms of web browser analogy, you don not always have to start from scratch. The web browser can keep states between sessions as open tabs (implicit) or bookmarks (explicit).
Your REST-driven application can also do the same. It would be the client's responsibility to keep track of its state so that the right requests can be made to rebuild the UI. This should have no bearing on the API itself, as it doesn't really care.
@tpluscode I thought you always have to start from the entry point, because the URIs can change. So if you save an URI, then it can break later. I'd rather save the id and get the actual URI template for it from the API.
@alien-mcl Sure, it is up to the client, but what if the client cannot figure out how to reach a certain state on its own?
Maybe my description of the problem was not clear enough. I try it again.
For example you find product 123 on the 12th page of the catalog, it has a link of https://api.example.com/v1/catalog/product/123
. You check the product details with the Catalog.getProduct({"productId": 123})
(where the catalog is a Hydra Resource, getProduct is an operation, productId is a property of the APIs vocab). You like the product so you save it in the favourites on the REST client. So the client will know that next time you want to reach the product from the favourites you need to use the Catalog.getProduct
operation again with the {"productId": 123}
input. Meanwhile the URIs change and you got https://api.example.com/v1/product/123
for the same resource and the old URI no longer works. How do you get a working link with the new URI for your favourite product? As far as I understand you need to find somehow the Catalog
first and get some sort of searchByProductId(123)
operation, which will give you a link to the getProduct({productId: 123})
operation. The origional way you found that link was totally different. How do you solve this problem based on the documentation automatically? Or should I just save the link that can break and respond in these cases with a "sorry pal, spend your money somewhere else" error message? Maybe I misunderstood your answer and you meant the API documentation for the developers, not the API documentation for the machines described with Hydra vocab.
with hydra the only stable URL would be an API Documentation's URL
Are you 💯 percent sure about that? I've always assumed that the entrypoint should be the stable point of contact.
You check the product details with the
Catalog.getProduct({"productId": 123})
This already presents a problem. The client should have no understanding that 123
is some kind of identifier. The only real identifier is the URL.
Thus, once you bookmark https://api.example.com/v1/catalog/product/123
, you can return to it simply by requesting that very resource. You do not need to find somehow the Catalog first and get some sort of searchByProductId(123) operation
Meanwhile the URIs change [...] Or should I just save the link that can break and respond in these cases with a "sorry pal, spend your money somewhere else" error message?
Now this is not really the client's problem to solve. I'd say that the responsibility of a well-designed API is to provide redirects, at least for a certain period following the change. You could then monitor for incoming requests and once the requests for the "old" URLs drops below a "low enough" threshold, you would then remove them completely.
Are you 💯 percent sure about that? I've always assumed that the entrypoint
should be the stable point of contact.
It can be moved to i.e. another sub-domain, or port or whatever. I wouldn't rely on it as immutable. API changes over time, some times it happens that one API is sucked into another.
This already presents a problem.
The client should have no understanding that 123 is some kind of identifier.
The only real identifier is the URL.
The resource's URL should be bookmarked - this way you don't need understanding anything else. Just obtain resource under that URL and you're all set.
Maybe I misunderstood your answer and you meant the API documentation for the developers,
not the API documentation for the machines described with Hydra vocab.
It is for both, developers and machines. Actually it would be also for users - you can use API documentation elements to provide human readable UI.
As for bookmarks - API documentation is not that necessary as you can see. Resolving a bookmarked URL may be enough. Only in case of an issue with resolution (i.e. 404 Not Found), server should come with a response with API documentation included so you can fall back i.e. to home page or as you wrote - provide some error message.
This already presents a problem. The client should have no understanding that 123 is some kind of identifier. The only real identifier is the URL.
I don't remember writing that the client knows that 123 is an identifier. All the client knows that the URI is build with {productId: 123}
and with an URI template. It assumes that the parameters of the template does not change when the URI changes, just the URI template itself. But nice catch, because that assumption does not have to hold. If so, then I think the only option to find that resource again is having some sort of service that converts to old link into the new one. I think that should be a bookmarking service sort of.
Now this is not really the client's problem to solve. I'd say that the responsibility of a well-designed API is to provide redirects, at least for a certain period following the change. You could then monitor for incoming requests and once the requests for the "old" URLs drops below a "low enough" threshold, you would then remove them completely.
Yes, I think that is a good solution too. I think even better than having a service for this. Still I don't feel right. I mean in both cases it would be nice to notify somehow the client that the link is deprecated. Is there any option for that?
@alien-mcl
Hmm I think a fairly good solution would be redirecting for a while, after that returning 404 with the new link in the error message and finally returning only 404. Any opinion about this?
I don't remember writing that the client knows that 123 is an identifier. All the client knows that the URI is build with {productId: 123} and with an URI template
Yes, that is precisely the point I was referring to. How is that 123
ever relevant going forward? The client best only remembers the URI and should not car about the template and its parameters any more.
I mean in both cases it would be nice to notify somehow the client that the link is deprecated. Is there any option for that?
Indeed I think that the sunset
header which @dret proposes is the solution you're looking for.
@tpluscode Thanks! That's exactly what I was looking for.
@dret
Yes, I closed this too early.
I read the description of the sunset header and it is good only if I want to remove the resource and I should write 410 gone after that.
I am not sure if the deprecation header is a solution here. I mean the resource is still available, just the URI changed. Afaik you can have multiple URIs for the same resource, so changing the URI does not mean that you have removed a resource and added a new one. Actually if we keep the old URI for a while and have a new URI too, then we have a scenario where 2 URIs identify 1 resource.
I think I'll stick with 301 moved and a redirection and later change it to 404.
@dret Thanks, still it was useful, I did not know about those headers. I think we can close this.