Exposing a service with a linked resource
Consider two simple JPA based entities, Course and Teacher:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | @Entity @Table (name = "teachers" ) public class Teacher { @Id @GeneratedValue (strategy = GenerationType.AUTO) @Column (name = "id" ) private Long id; @Size (min = 2 , max = 50 ) @Column (name = "name" ) private String name; @Column (name = "department" ) @Size (min = 2 , max = 50 ) private String department; ... } @Entity @Table (name = "courses" ) public class Course { @Id @GeneratedValue (strategy = GenerationType.AUTO) @Column (name = "id" ) private Long id; @Size (min = 1 , max = 10 ) @Column (name = "coursecode" ) private String courseCode; @Size (min = 1 , max = 50 ) @Column (name = "coursename" ) private String courseName; @ManyToOne @JoinColumn (name = "teacher_id" ) private Teacher teacher; .... } |
essentially the relation looks like this:
Now, all it takes to expose these entities as REST resources is adding a @RepositoryRestResource annotation on their JPA based Spring Data repositories this way, first for the "Teacher" resource:
1 2 3 4 5 6 7 | import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.rest.core.annotation.RepositoryRestResource; import univ.domain.Teacher; @RepositoryRestResource public interface TeacherRepo extends JpaRepository<Teacher, Long> { } |
and for exposing the Course resource:
1 2 3 | @RepositoryRestResource public interface CourseRepo extends JpaRepository<Course, Long> { } |
With this done and assuming a few teachers and a few courses are already in the datastore, a GET on courses would yield a response of the following type:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | { "_links" : { "self" : { "templated" : true } }, "_embedded" : { "courses" : [ { "courseCode" : "Course1" , "courseName" : "Course Name 1" , "version" : 0 , "_links" : { "self" : { }, "teacher" : { } } }, { "courseCode" : "Course2" , "courseName" : "Course Name 2" , "version" : 0 , "_links" : { "self" : { }, "teacher" : { } } } ] }, "page" : { "size" : 20 , "totalElements" : 2 , "totalPages" : 1 , "number" : 0 } } |
and a specific course looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 | { "courseCode" : "Course1" , "courseName" : "Course Name 1" , "version" : 0 , "_links" : { "self" : { }, "teacher" : { } } } |
If you are wondering what the "_links", "_embedded" are - Spring Data REST uses Hypertext Application Language(or HAL for short) to represent the links, say the one between a course and a teacher.
HAL Based REST service - Using RestTemplate
Given this HAL based REST service, the question that I had in my mind was how to write a client to this service. I am sure there are better ways of doing this, but what follows worked for me and I welcome any cleaner ways of writing the client.
First, I modified the RestTemplate to register a custom Json converter that understands HAL based links:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public RestTemplate getRestTemplateWithHalMessageConverter() { RestTemplate restTemplate = new RestTemplate(); List<HttpMessageConverter<?>> existingConverters = restTemplate.getMessageConverters(); List<HttpMessageConverter<?>> newConverters = new ArrayList<>(); newConverters.add(getHalMessageConverter()); newConverters.addAll(existingConverters); restTemplate.setMessageConverters(newConverters); return restTemplate; } private HttpMessageConverter getHalMessageConverter() { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule( new Jackson2HalModule()); MappingJackson2HttpMessageConverter halConverter = new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport. class ); halConverter.setSupportedMediaTypes(Arrays.asList(HAL_JSON)); halConverter.setObjectMapper(objectMapper); return halConverter; } |
The Jackson2HalModule is provided by the Spring HATEOS project and understands HAL representation.
Given this shiny new RestTemplate, first let us create a Teacher entity:
1 2 3 4 5 | Teacher teacher1 = new Teacher(); teacher1.setName( "Teacher 1" ); teacher1.setDepartment( "Department 1" ); URI teacher1Uri = |
Note that when the entity is created, the response is a http status code of 201 with the Location header pointing to the uri of the newly created resource, Spring RestTemplate provides a neat way of posting and getting hold of this Location header through an API. So now we have a teacher1Uri representing the newly created teacher.
Given this teacher URI, let us now retrieve the teacher, the raw json for the teacher resource looks like the following:
1 2 3 4 5 6 7 8 9 10 | { "name" : "Teacher 1" , "department" : "Department 1" , "version" : 0 , "_links" : { "self" : { } } } |
and to retrieve this using RestTemplate:
1 2 3 4 5 6 7 8 9 10 | ResponseEntity<Resource<Teacher>> teacherResponseEntity = testRestTemplate.exchange( "http://localhost:8080/api/teachers/1" , HttpMethod.GET, null , new ParameterizedTypeReference<Resource<Teacher>>() { }); Resource<Teacher> teacherResource = teacherResponseEntity.getBody(); Link teacherLink = teacherResource.getLink( "self" ); String teacherUri = teacherLink.getHref(); Teacher teacher = teacherResource.getContent(); |
Jackson2HalModule is the one which helps unpack the links this cleanly and to get hold of the Teacher entity itself. I have previously explained ParameterizedTypeReference here.
Now, to a more tricky part, creating a Course.
Creating a course is tricky as it has a relation to the Teacher and representing this relation using HAL is not that straightforward. A raw POST to create the course would look like this:
1 2 3 4 5 6 | { "courseCode" : "Course1" , "courseName" : "Course Name 1" , "version" : 0 , } |
Note how the reference to the teacher is a URI, this is how HAL represents an embedded reference specifically for a POST'ed content, so now to get this form through RestTemplate -
First to create a Course:
1 2 3 | Course course1 = new Course(); course1.setCourseCode( "Course1" ); course1.setCourseName( "Course Name 1" ); |
At this point, it will be easier to handle providing the teacher link by dealing with a json tree representation and adding in the teacher link as the teacher uri:
1 2 3 | ObjectMapper objectMapper = getObjectMapperWithHalModule(); ObjectNode jsonNodeCourse1 = (ObjectNode) objectMapper.valueToTree(course1); jsonNodeCourse1.put( "teacher" , teacher1Uri.getPath()); |
and posting this should create the course with the linked teacher:
1 | URI course1Uri = testRestTemplate.postForLocation(coursesUri, jsonNodeCourse1); |
and to retrieve this newly created Course:
1 2 3 4 5 6 | ResponseEntity<Resource<Course>> courseResponseEntity = testRestTemplate.exchange(course1Uri, HttpMethod.GET, null , new ParameterizedTypeReference<Resource<Course>>() { }); Resource<Course> courseResource = courseResponseEntity.getBody(); Link teacherLinkThroughCourse = courseResource.getLink( "teacher" ); |
This concludes how to use the RestTemplate to create and retrieve a linked resource, alternate ideas are welcome.
If you are interested in exploring this further, the entire sample is available at this github repo - and the test is here
References:
Hypertext Application Language(or HAL for short)HAL Specification
Spring RestTemplate