= Spring HATEOAS - Basic Example
This guides shows a core example of using Spring HATEOAS. It illustrates how to sew hypermedia into your Spring MVC application, including test.
Start with a very simple example, a payroll system that tracks employees.
NOTE: This example uses https://projectlombok.org[Project Lombok] to reduce writing Java code.
== Defining Your Domain
The cornerstone of any example is the domain object:
[source,java]
----
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
class Employee implements Identifiable<Long> {
@Id @GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String role;
...
}
----
This domain object includes:
* `@Data` - Lombok annotation to define a mutable value object
* `@Entity` - JPA annotation to make the object storagable in a classic SQL engine (H2 in this example)
* `@NoArgsConstructor` - Lombok annotation to create an empty constructor call to appear Jackson.
* `@AllArgsConstructor` - Lombok annotation to create an all-arg constructor for certain test scenarios
* `@JsonIgnoreProperties(ignoreUnknown=true)` - Jackson annotation to ignore unknown attributes when deserializing JSON.
It also implements `Identifiable<Long>`, which comes with a single method, `getId`, used to fetch the unique identifier
for a given entity.
NOTE: Make your REST resources robust by instructing Jackson to ignore unknown attributes. That way, additional elements,
like hypermedia (HAL *_links*, HAL-Forms *_templates*, etc.) and newer attributes will be discarded.
This means a HAL document like this:
----
{
"firstName": "Frodo",
"lastName": "Baggins",
"role" : "ring bearer",
"_links" : {
"self" : {
"href" : "/employees/1"
}
}
}
----
...which was served up as hypermedia, can be POST'd to create a new employee. The *_links* will simply be ignored.
== Accessing Data
To experiment with something realistic, you need to access a real database. This example leverages H2, an embedded JPA datasource.
And while it's not a requirement for Spring HATEOAS, this example uses Spring Data JPA.
Create a repository like this:
[source,java]
----
interface EmployeeRepository extends CrudRepository<Employee, Long> {
}
----
This interface extends Spring Data Commons' `CrudRepository`, inheriting a collection of create/replace/update/delete (CRUD)
operations.
== Converting Entities to Resources
In REST, the "thing" being linked to is a *resource*. Resources provide both information as well as information on _how_ to
retrieve and update that information.
Spring HATEOAS defines a generic `Resource<T>` container that lets store any domain object (`Employee` in this example), and
add additional links.
IMPORTANT: Spring HATEOAS's `Resource` and `Link` classes are *vendor neutral*. HAL is thrown around a lot, being the
default mediatype, but the classes you are using can be used to render any mediatype.
A common convention is to create a factory used to convert `Employee` objects into `Resource<Employee>` objects. Spring
HATEOAS provides `SimpleIdentifiableResourceAssembler<T>` as the simplest mechanism to perform these conversions.
[source,java]
----
@Component
class EmployeeResourceAssembler extends SimpleIdentifiableResourceAssembler<Employee> {
/**
* Link the {@link Employee} domain type to the {@link EmployeeController} using this
* {@link SimpleIdentifiableResourceAssembler} in order to generate both {@link org.springframework.hateoas.Resource}
* and {@link org.springframework.hateoas.Resources}.
*/
EmployeeResourceAssembler() {
super(EmployeeController.class);
}
}
----
This class has two key properties:
* The generic type (`Employee`) declares the entity type.
* The constructor defines the Spring MVC controller (`EmployeeController`) where links are found to turn the entity into a REST resource.
The class is flagged with Spring's `@Component` annotation, flagging it to be automatically hooked into the
application context.
This is half the battle. This resource assembler is used _in the controller_ to assemble REST resources, as shown in the next section.
== Creating Links
The following Spring MVC controller defines the application's routes, and hence is the source of links needed
in the hypermedia.
NOTE: This guide assumes you already somewhat familiar with Spring MVC.
[source,java]
----
@RestController
class EmployeeController {
private final EmployeeRepository repository;
private final EmployeeResourceAssembler assembler;
EmployeeController(EmployeeRepository repository, EmployeeResourceAssembler assembler) {
this.repository = repository;
this.assembler = assembler;
}
...
}
----
This piece of code shows how the Spring MVC controller is wired with a copy of the `EmployeeRepository` as well as a
`EmployeeResourceAssembler` and marked as a *REST controller* thanks to the `@RestController` annotation.
To support `SimpleIdentifiableResourceAssembler`, the controller needs two things:
* A route to the collection. By default, it assumes a pluralized, lowercased name (`Employee` -> `/employees`).
* A route to a single entity. By default it assumes the collection's URI + `/{id}`.
The collection's route is shown below:
[source,java]
----
/**
* Look up all employees, and transform them into a REST collection resource using
* {@link EmployeeResourceAssembler#toResources(Iterable)}. Then return them through
* Spring Web's {@link ResponseEntity} fluent API.
*
* NOTE: cURL will fetch things as HAL JSON directly, but browsers issue a different
* default accept header, which allows XML to get requested first, so "produces"
* forces it to HAL JSON for all clients.
*/
@GetMapping(value = "/employees", produces = MediaTypes.HAL_JSON_VALUE)
public ResponseEntity<Resources<Resource<Employee>>> findAll() {
return ResponseEntity.ok(
assembler.toResources(repository.findAll()));
}
----
It uses the `EmployeeResourceAssembler` and it's `toResources(Employee)` method to turn a collection of
`Employee` objects into a `Resources<Resource<Employee>>`.
NOTE: `Resources` is Spring HATEOAS's vendor neutral representation of a collection. It has it's
own collection of links, separate from the links of every member of the collection. That's why the whole
structure is `Resources<Resource<Employee>>` and not `Resources<Employee>`.
To build a single resource, the `/employess/{id}` route is shown below:
[source,java]
----
/**
* Look up a single {@link Employee} and transform it into a REST resource using
* {@link EmployeeResourceAssembler#toResource(Object)}. Then return it through
* Spring Web's {@link ResponseEntity} fluent API.
*
* See {@link #findAll()} to explain {@link GetMapping}'s "produces" argument.
*
* @param id
*/
@GetMapping(value = "/employees/{id}", produces = MediaTypes.HAL_JSON_VALUE)
public ResponseEntity<Resource<Employee>> findOne(@PathVariable long id) {
return ResponseEntity.ok(
assembler.toResource(repository.findOne(id)));
}
----
Again, the `EmployeeResourceAssembler` is used to convert a single `Employee` into a `Resource<Employee>`
through its `toResource(Employee)` method.
== Customizing the Output
What's not shown in this example is that the `EmployeeResourceAssembler` comes with overrides.
* `setBasePath(/* base */)` would inject a prefix into every link built in the hypermedia.
* `addLinks(Resource<T>)` and `addLinks(Resources<T>)` allows you to override/augment the default links assigned to every resource.
* `getCollectionLinkBuilder()` lets you override the convention of how the whole route is built up.
== Testing Hypermedia
Nothing is complete without testing. Thanks to Spring Boot, it's easier than ever to test a Spring MVC controller,
including the generated hypermedia.
The following is a bare bones "slice" test case:
[source,java]
----
@RunWith(SpringRunner.class)
@WebMvcTest(EmployeeController.class)
@Import({EmployeeResourceAssembler.class})
public class EmployeeControllerTests {
@Autowired
private MockMvc mvc;
@MockBean
private EmployeeRepository repository;
...
}
----
* `@RunWith(SpringRunner.class)` is needed to leverage Spring Boot's test annotations with JUnit.
* `@WebMvcTest(EmployeeController.class)` confines Spring Boot to only autoconfiguring Spring MVC components, and _only_
this one controller, making it a very precise test case.
* `@Import({EmployeeResourceAssembler.class})` pulls in one extra Spring component that would be ignored by `@WebMvcTest`.
* `@Autowired MockMvc` gives us a handle on a Spring Mock tester.
* `@MockBean` flags `EmployeeRepositor` as a test collaborator.
With this structure, we can start crafting a test case!
[source,java]
----
@Test
public void getShouldFetchAHalDocument() throws Exception {
given(repository.findAll()).willReturn(
Arrays.asList(
new Employee(1L,"Frodo", "Baggins", "ring bearer"),
new Employee(2L,"Bilbo", "Baggins", "burglar")));
mvc.perform(get("/employees").accept(MediaTypes.HAL_JSON_VALUE))
.andDo(print())
.andExpect(status().isOk())
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, MediaTypes.HAL_JSON_VALUE + ";charset=UTF-8"))
.andExpect(jsonPath("$._embedded.employees[0].id", is(1)))
...
}
----
* At first, the test case uses Mockito's `given()` method to define the "given"s of the test.
* Next, it uses Spring Mock MVC's `mvc` to `perform()` a *GET /employees* call with an accept header of HAL's mediatype.
* As a courtesy, it uses the `.andDo(print())` to give us a complete print out of the whole thing on the console.
* Finally, it chains a whole series of assertions.
** Verify HTTP status is *200 OK*.
** Verify the response *Content-Type* header is also HAL's mediatype.
** Verify that the JSON Path of *$._embedded.employees[0].id* is `1`.
The rest of the assertions are commented out, but you can read it in the source code.
NOTE: This is not the only way to assert the results. See Spring Framework reference docs and Spring HATEOAS
test cases for more examples.