REST Service Discoverability with Spring, part 5
Decouple Discoverability through events
Discoverability as a separate aspect or concern of the web layer should be decoupled from the controller handling the HTTP request. In order to do so, the Controller will fire off events for all the actions that require additional manipulation of the HTTP response:
@RequestMapping( value = "admin/foo/{id}",method = RequestMethod.GET ) @ResponseBody public Foo get( @PathVariable( "id" ) Long id, HttpServletRequest request, HttpServletResponse response ){ Foo resourceById = RestPreconditions.checkNotNull( this.service.getById( id ) ); this.eventPublisher.publishEvent ( new SingleResourceRetrieved( this, request, response ) ); return resourceById; } @RequestMapping( value = "admin/foo",method = RequestMethod.POST ) @ResponseStatus( HttpStatus.CREATED ) public void create( @RequestBody Foo resource, HttpServletRequest request, HttpServletResponse response ){ RestPreconditions.checkNotNullFromRequest( resource ); Long idOfCreatedResource = this.service.create( resource ); this.eventPublisher.publishEvent ( new ResourceCreated( this, request, response, idOfCreatedResource ) ); }
These events can then be handled by any number of decoupled listeners, each focusing on it’s own particular case and each moving towards satisfying the overall HATEOAS constraint.
Also, the listeners should be the last objects in the call stack and no direct access to them is necessary; as such they are not public.
Make the URI of a newly created resource discoverable
As discussed in the previous post, the operation of creating a new resource should return the URI of that resource in the Location HTTP header of the response. :
@Component class ResourceCreatedDiscoverabilityListener implements ApplicationListener< ResourceCreated >{ @Override public void onApplicationEvent( ResourceCreated resourceCreatedEvent ){ Preconditions.checkNotNull( resourceCreatedEvent ); HttpServletRequest request = resourceCreatedEvent.getRequest(); HttpServletResponse response = resourceCreatedEvent.getResponse(); long idOfNewResource = resourceCreatedEvent.getIdOfNewResource(); this.addLinkHeaderOnResourceCreation( request, response, idOfNewResource ); } void addLinkHeaderOnResourceCreation ( HttpServletRequest request, HttpServletResponse response, long idOfNewResource ){ String requestUrl = request.getRequestURL().toString(); URI uri = new UriTemplate( "{requestUrl}/{idOfNewResource}" ) .expand( requestUrl, idOfNewResource ); response.setHeader( HttpHeaders.LOCATION, uri.toASCIIString() ); } }
Unfortunately, dealing with the low level request and response objects is inevitable even in Spring 3.1, because first class support for specifying the Location is still in the works.
Get of single resource
Retrieving a single resource should allow the client to discover the URI to get all resources of that particular type:
@Component class SingleResourceRetrievedDiscoverabilityListener implements ApplicationListener< SingleResourceRetrieved >{ @Override public void onApplicationEvent( SingleResourceRetrieved resourceRetrievedEvent ){ Preconditions.checkNotNull( resourceRetrievedEvent ); HttpServletRequest request = resourceRetrievedEvent.getRequest(); HttpServletResponse response = resourceRetrievedEvent.getResponse(); this.addLinkHeaderOnSingleResourceRetrieval( request, response ); } void addLinkHeaderOnSingleResourceRetrieval ( HttpServletRequest request, HttpServletResponse response ){ StringBuffer requestURL = request.getRequestURL(); int positionOfLastSlash = requestURL.lastIndexOf( "/" ); String uriForResourceCreation = requestURL.substring( 0, positionOfLastSlash ); String linkHeaderValue = RESTURLUtil .createLinkHeader( uriForResourceCreation, "collection" ); response.addHeader( LINK_HEADER, linkHeaderValue ); } }
Note that the semantics of the link relation make use of the “collection” relation type, specified and used in several microformats, but not yet standardized.
The Link header is one of the most used HTTP header for the purposes of discoverability. Because of this, some simple utilities are needed to ease the creation of it’s values on the server and to avoid introducing a third party library.
Discoverability at the root
The root is the entry point in the RESTful web service – it is what the client comes into contact with when consuming the API for the first time. If the HATEOAS constraint is to be considered and implemented throughout, then this is the place to start. The fact that most of the main URIs of the system have to be discoverable from the root shouldn’t come as much of a surprise by this point.
This is a sample controller method to provide discoverability at the root:
@RequestMapping( value = "admin",method = RequestMethod.GET ) @ResponseStatus( value = HttpStatus.NO_CONTENT ) public void adminRoot( HttpServletRequest request, final response ){ String rootUri = request.getRequestURL().toString(); URI fooUri = new UriTemplate( "{rootUri}/{resource}" ).expand( rootUri, "foo" ); String linkToFoo = RESTURIUtil.createLinkHeader ( fooUri.toASCIIString(), REL_COLLECTION ); response.addHeader( HttpConstants.LINK_HEADER, linkToFoo ); }
This is of course an illustration of the concept, to be read in the context of the proof of concept RESTful service of the series. In a more complex system there would be many more links, each with it’s own semantics defined by the type of link relation.
Discoverability is not about changing URIs
One of the more common pitfalls related to discoverability is the misunderstanding that, since the URIs are now discoverable, then they can be subject to change. This is however simply not the case, and for good reason: first, this is not how the web works – clients will bookmark the URIs and will expect them to work in the future. Second, the client shouldn’t have to navigate through the API to get to a certain state that could have been reached directly.
Instead, all URIs of the RESTful web service should be considered cool URIs, and cool URIs don’t change. Instead, versioning of the API can be used to solve the problem of a URI reorganization.
Caveats of Discoverability
As some of the discussions around the previous articles state, the first goal of discoverability is to make minimal or no use of documentation and have the client learn and understand how to use the API via the responses it gets. In fact, this shouldn’t be regarded as such a far fetched ideal – it is how we consume every new web page – without any documentation. So, if the concept is more problematic in the context of REST, then it must be a matter of technical implementation, not of a question of whether or not it’s possible.
That being said, technically, we are still far from the a fully working solution – the specification and framework support are still evolving, and because of that, some compromises may have to be made; these are nevertheless compromises and should be regarded as such.
Conclusion
This article covered the implementation of some of the traits of discoverability in the context of a RESTful Service with Spring MVC and touched on the concept of discoverability at the root. In the next articles I will focus on custom link relations and the Atom Publishing Protocol. In the meantime, check out the github project.
Reference: REST Service Discoverability with Spring, part 5 from our JCG partner Eugen Paraschiv at the baeldung blog.
Related Articles :
- Bootstrapping a web application with Spring 3.1 and Java based Configuration, part 1
- Building a RESTful Web Service with Spring 3.1 and Java based Configuration, part 2
- Securing a RESTful Web Service with Spring Security 3.1, part 3
- RESTful Web Service Discoverability, part 4
- Basic and Digest authentication for a RESTful Service with Spring Security 3.1, part 6
- Spring & Quartz Integration with Custom Annotation
- Spring MVC Interceptors Example
- Swapping out Spring Bean Configuration at Runtime