Skip to content

Latest commit

 

History

History

simplified

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

Spring HATEOAS - Basic Example

This guides shows how to add Spring HATEOAS in the simplest way possible. Like the rest of these examples, it uses a payroll system.

Note
This example uses Project Lombok to reduce writing Java code.

Defining Your Domain

The cornerstone of any example is the domain object:

@Data
@Entity
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor
class Employee {

	@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(PRIVATE) - Lombok annotation to create an empty constructor call to appease Jackson, but which is private and not usable to our app’s code.

  • @AllArgsConstructor - Lombok annotation to create an all-arg constructor for certain test scenarios

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:

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 details on how to retrieve and update that information.

Spring HATEOAS defines a generic EntityModel<T> container that lets you 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 media type, but these classes can be used to render any media type.

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.
@RestController
class EmployeeController {

	private final EmployeeRepository repository;

	EmployeeController(EmployeeRepository repository) {
		this.repository = repository;
	}

	...
}

This piece of code shows how the Spring MVC controller is wired with a copy of the EmployeeRepository through constructor injection and marked as a REST controller thanks to the @RestController annotation.

The route for the aggregate root is shown below:

/**
 * Look up all employees, and transform them into a REST collection resource.
 * Then return them through Spring Web's {@link ResponseEntity} fluent API.
 */
@GetMapping("/employees")
ResponseEntity<CollectionModel<EntityModel<Employee>>> findAll() {

	List<EntityModel<Employee>> employees = StreamSupport.stream(repository.findAll().spliterator(), false)
		.map(employee -> EntityModel.of(employee,
			linkTo(methodOn(EmployeeController.class).findOne(employee.getId())).withSelfRel(),
			linkTo(methodOn(EmployeeController.class).findAll()).withRel("employees")))
		.collect(Collectors.toList());

	return ResponseEntity.ok(
		CollectionModel.of(employees,
			linkTo(methodOn(EmployeeController.class).findAll()).withSelfRel()));
}

It retrieves a collection of Employee objects, streams through a Java 8 spliterator, and converts them into a collection of EntityModel<Employee> objects by using Spring HATEOAS’s linkTo and methodOn helpers to build links.

  • The natural convention with REST endpoints is to serve a self link (denoted by the .withSelfRel() call).

  • It’s also useful for any single item resource to include a link back to the aggregate (denoted by the .withRel("employees")).

The whole collection of single item resources is then wrapped in a Spring HATEOAS Resources type.

Note
Resources is Spring HATEOAS’s vendor neutral representation of a collection. It has it’s own set of links, separate from the links of each member of the collection. That’s why the whole structure is CollectionModel<EntityModel<Employee>> and not CollectionModel<Employee>.

To build a single resource, the /employees/{id} route is shown below:

/**
 * Look up a single {@link Employee} and transform it into a REST resource. Then return it through
 * Spring Web's {@link ResponseEntity} fluent API.
 *
 * @param id
 */
@GetMapping("/employees/{id}")
ResponseEntity<EntityModel<Employee>> findOne(@PathVariable long id) {

	return repository.findById(id)
		.map(employee -> EntityModel.of(employee,
			linkTo(methodOn(EmployeeController.class).findOne(employee.getId())).withSelfRel(),
			linkTo(methodOn(EmployeeController.class).findAll()).withRel("employees")))
		.map(ResponseEntity::ok)
		.orElse(ResponseEntity.notFound().build());
}

This code is almost identical. It fetches a single item Employee from the database and that wraps up into a EntityModel<Employee> object with the same links, but that’s it. No need to create a Resources object since is NOT a collection.

Important
Does this look like duplicate code found in the aggregate root? Sures it does. That’s why Spring HATEOAS includes the ability to define a ResourceAssembler. It lets you define, in one place, all the links for a given entity type. Then you can reuse it as needed in all relevant controller methods. It’s been left out of this section for the sake of simplicity.

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:

@RunWith(SpringRunner.class)
@WebMvcTest(EmployeeController.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.

  • @Autowired MockMvc gives us a handle on a Spring Mock tester.

  • @MockBean flags EmployeeRepository as a test collaborator, since we don’t plan on talking to a real database in this test case.

With this structure, we can start crafting a test case!

@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_UTF8_VALUE))
		.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 media type.

  • 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 media type (with UTF-8 flavor).

    • Verify that the JSON Path of $._embedded.employees[0].id is 1.

    • And so forth…​

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.

For the next step in Spring HATEOAS, you may wish to read Spring HATEOAS - API Evolution Example.