Files
spring-hateoas-examples/basics
2017-06-06 11:21:57 -05:00
..
2017-05-30 12:11:17 -05:00
2017-06-06 11:21:57 -05:00

= 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 appease 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]]
== 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 these classes 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 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.

== 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(Iterable<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 set of links, separate from the links of each member of the collection. That's why the whole
structure is `Resources<Resource<Employee>>` and not `Resources<Employee>`.

To build a single resource, the `/employees/{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.