Skip to content

Latest commit

 

History

History

basics

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

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 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
@JsonIgnoreProperties(ignoreUnknown = true)
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(PROTECTED) - Lombok annotation to create an empty constructor call to appease JPA, but which is protected and not usable to our app’s code.

  • @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.

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:

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 EntityModel<T> container used to model resources that lets store any domain object (Employee in this example), and add additional links.

Important
Spring HATEOAS’s EntityModel 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.

A common convention is to create a factory used to convert Employee objects into EntityModel<Employee> objects. Spring HATEOAS provides SimpleIdentifiableRepresentationModelAssembler<T> as the simplest mechanism to perform these conversions.

@Component
class EmployeeRepresentationModelAssembler extends SimpleIdentifiableRepresentationModelAssembler<Employee> {

	/**
	 * Link the {@link Employee} domain type to the {@link EmployeeController} using this
	 * {@link SimpleIdentifiableRepresentationModelAssembler} in order to generate both {@link org.springframework.hateoas.EntityModel}
	 * and {@link org.springframework.hateoas.CollectionModel}.
	 */
	EmployeeRepresentationModelAssembler() {
		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 so that it will be automatically hooked into the application context.

This is half the battle, already solved. This resource assembler is used in the controller to assemble REST resources, as shown in the next section.

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;
	private final EmployeeRepresentationModelAssembler assembler;

	EmployeeController(EmployeeRepository repository, EmployeeRepresentationModelAssembler 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 EmployeeRepresentationModelAssembler and marked as a REST controller thanks to the @RestController annotation.

To support SimpleIdentifiableRepresentationModelAssembler, 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:

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

}

It uses the EmployeeRepresentationModelAssembler and it’s toCollectionModel(Iterable<Employee>) method to turn a collection of Employee objects into a CollectionModel<EntityModel<Employee>>.

Note
CollectionModel 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 using
 * {@link EmployeeRepresentationModelAssembler#toEntityModel(Object)}. Then return it through
 * Spring Web's {@link ResponseEntity} fluent API.
 *
 * @param id
 */
@GetMapping("/employees/{id}")
public ResponseEntity<EntityModel<Employee>> findOne(@PathVariable long id) {
	return this.repository.findById(id) //
            .map(this.assembler::toModel) //
            .map(ResponseEntity::ok) //
            .orElse(ResponseEntity.notFound().build());
}

Again, the EmployeeRepresentationModelAssembler is used to convert a single Employee into a EntityModel<Employee> through its toEntityModel(Employee) method.

Customizing the Output

What’s not shown in this example is that the EmployeeRepresentationModelAssembler comes with overrides.

  • setBasePath(/* base */) would inject a prefix into every link built in the hypermedia.

  • addLinks(EntityModel<T>) and addLinks(CollectionModel<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:

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

@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))
		.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.

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

The rest of the assertions are not shown above, 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.