Spring-hateoas: How to fech JPA entity from HATEOAS URI (self.href)

Created on 19 Dec 2018  Â·  8Comments  Â·  Source: spring-projects/spring-hateoas

I really like the HATEOAS concept that URIs are used as IDs. For example when I have an POJO Entity Comment it can simply be referenced as /myBasePath/comments/4711

spring-hateoas has great support for building those URIs when I have an JPA entity:

Link entityUriLink = entityLinks.linkToSingleResource(Comment.class, comment.getId());

BUT there is no way for the other way round. There no (easy) way to get the JPA entity when you just have its URI.

Why and when is this necessary? In a custom rest controller, an enttiy URI might be passed as a @RequestParam. Spring data rest actually IS able to fetch the entity from its plain (internal, numerical) ID. This works:

@BasePathAwareController  
public class MyCustomController {
  @RequestMapping("/customEndpoint")
  public ResponseEntity customEndpoint(@RequestParam("commentId") CommentModel comment) { ... }

}

GET /myBasePath/customEndpoint?commentId=4711   <= works
GET /myBasePath/customEndpoint?commentId=/comments/4711   <= DOES NOT WORK
GET /myBasePath/customEndpoint?commentId=/myBasePath/comments/4711   <= DOES NOT WORK
GET /myBasePath/customEndpoint?commentId=http://localhost:8080/myBasePath/comments/4711   <= DOES NOT WORK

There would be org.springframework.data.rest.core.UriToEntityConverter but this class is very difficult to create, because it needs so many dependencies (PersistentEntities, RepositoryInvokerFactory, Repositories)

I have a workaround, but an ugly one. You can configure your own conversionService in a RepositoryRestConfigurer. But then its an ugly parsing of IDs from String in there.

=> How to get the JPA entity when I only have the URI of it?

Most helpful comment

Greg, this has been a concern that I brought up with version 0.11 or so. Without the ability to do round-trip mapping, the entire infrastructure is essentially useless because link handling devolves into a bunch of hand-parsing.

All 8 comments

Sorry, mate.

The heart of the issue is that there is no central table of routes in Spring MVC to go and do a reverse lookup like that.

In Spring Data Rest where we control the routes we have something. But with custom routes and in pure Spring HATEOAS where routes are a free for all, we’ve got nothing.

Greg, this has been a concern that I brought up with version 0.11 or so. Without the ability to do round-trip mapping, the entire infrastructure is essentially useless because link handling devolves into a bunch of hand-parsing.

As you've discovered, Spring Data REST has quite a bit of extra knowledge it can use to do that reverse lookup. That's why UriToEntityConverter has so many additional dependencies.

What you imagine would effectively require is a capability in Spring MVC to use different HandlerMethodArgumentResolvers based on the structure of the value for the given parameter / path segment. @rstoyanchev might correct me if I'm wrong but I am pretty sure, such a hook currently does not exist as the selection is based on the MethodParameter only.

Another approach might be to extend Spring Data's DomainClassConverter (which would render the problem a Spring Data one, not a Spring HATEOAS one) to inspect the incoming value, somehow find out it's supposed to be a URI, and – assuming EntityLinks exposed the URI template it promises to follow – extract the template variable and use that resolved value as identifier.

I can see that dragging quite a bit of complexity. At least it's not exactly trivial as it requires a couple of projects' APIs to interact with each other. I can certainly give this a spike, but I am not sure, we're gonna get there soon.

@chrylis – Do you have the ticket id handy for the issue you raised back in the days?

Thank you for your responses. I though so that this actually is a complex topic. Maybe another view on a more general level:

I really like the idea of having URIs as IDs for entities. Internally in the DB entities have numerical IDs. (e.g. 4711) and they can be referenced by those IDs. But externally the HATOAS API exposes a different kind of IDs. It exposes URIs that unambiguously identify resources. Those URIs just happen to have the syntactical form of URLs. They could have any other syntax too (e.g. "MyEntity-4711"). Of course URLs are practical, because you can GET and POST to them.

My point is: The component that decides on the syntax of those external IDs, the component that builds those IDs, should also be responsible to "parse" these IDs, in that one central place.

There is one central component that creates URIs like http://domain/apibasepath/blogs/1/posts/13/comments/12 This one component adds the "apibasepath" part, when configured to do so. This component decides to first add the entity name and than its (internal) numerical ID - which totally makes sense, but theoretical could also be done the other way round. This component decided to use the plural form of entity names in the URL. - which totally makes sense, but could also be implemented differently.

This same component should also be responsibel for the inverse parsing.

=> Did I understand this correctly that spring data rest (and not spring HATEOAS) is building these URIs? I actually thought that this whole URI concept is an HATEOAS concept. But just now I realized that UriToEntityConverter is a SDR class. Does SDR create those URIs? Then SDR should also have a simple public method to convert those URIs back and forth.

@odrotbohm: Discussion on https://github.com/spring-projects/spring-hateoas/issues/292#issuecomment-129969882, plus in-person conversations with Greg (and a quick question to you at S2GX).

@Doogiemuc in SDR, you have RepositoryEntityLinks that lets you turn domain objects into links. That's because SDR defines all the routes and can thus leverage metadata. And as you've noted, SDR also has UriToEntityConverter, which lets you go the other direction.

But with Spring HATEOAS, the only place where routes are defined are in the annotations in your own custom controllers. Spring MVC/WebFlux (the underlying web stack) has no centralized route table that this data is gathered into, hence no way to do a reverse lookup.

Was this page helpful?
0 / 5 - 0 ratings