Spring HATEOAS

repository·main·Indexed 22 days ago

https://github.com/spring-projects/spring-hateoas

A library for Spring MVC applications designed to simplify the creation of RESTful representations following the HATEOAS (Hypermedia as the Engine of Application State) principle. It provides APIs for automating link creation, representation assembly, and hypermedia-driven navigation using tools like Traverson, LinkDiscoverer, and RepresentationModel subtypes such as EntityModel and CollectionModel.

Tokens
11.3K
Snippets
34
Records
46
Agent score
76%

What's inside Spring HATEOAS

  1. Overview of Spring HATEOAS

    main
    Spring HATEOAS provides APIs to simplify the creation of REST representations that adhere to the HATEOAS (Hypermedia as the Engine of Application State) principle. It is specifically designed to work with Spring, particularly Spring MVC, by addressing the complexities of link creation and the assembly of hypermedia-driven representations.
  2. Understand the new package structure in Spring HATEOAS 1.0

    main

    The 1.0 release introduced a clear separation of APIs to support a hypermedia type registration API. The package structure is organized as follows:

    • Client APIs: Located in the client package (e.g., LinkDiscoverer has moved here).
    • Server APIs: Located in the server package (e.g., LinkBuilder and EntityLinks have moved here).
    • MVC Server APIs: ControllerLinkBuilder has moved to server.mvc and is deprecated in favor of WebMvcLinkBuilder.
    • Media Type Implementations: Located in the mediatype package.
  3. Use Links to indicate resource navigation

    main

    Spring HATEOAS uses the immutable Link value type to enrich resource representations with hypermedia. A link consists of a hypertext reference (the URI) and a link relation (the semantics of the relationship). By default, if no relation is provided, the link relation is set to the IANA self relation.

    You can use 'wither' methods on a Link instance to set additional attributes as defined in RFC-8288.

    // Basic link creation
    Link link = Link.of("/some-resource");
    
    // Link with a specific relation
    Link linkWithRel = Link.of("/some-resource", "next");
  4. Work with URI templates for parameterized links

    main

    A Link can contain a URI template (RFC-6570) instead of a static URI. This allows clients to expand template variables without knowing the final URI structure.

    Key capabilities:

    • The Link instance indicates it is templated.
    • It exposes the parameters contained in the template.
    • It allows expansion of those parameters into a concrete URI.
    // Manually constructing a URI template with a request parameter
    UriTemplate template = UriTemplate.of("/{segment}/something")
      .with(new TemplateVariable("parameter", VariableType.REQUEST_PARAM));
    
    assertThat(template.toString()).isEqualTo("/{segment}/something{?parameter}");
  5. Attach affordances to links

    main

    Affordances provide metadata about how to use a resource (e.g., which HTTP methods are available). You can attach affordances to a link using .andAffordance(afford(...)).

    Automatic Affordance Registration

    You can point to controller methods to automatically capture metadata about request bodies and response types:

    // Example: Associating a PUT and PATCH operation to a GET self link
    link.andAffordance(afford(employeeController::updateEmployee))
        .andAffordance(afford(employeeController::partiallyUpdateEmployee));

    Manual Affordance Registration

    If you need to build affordances manually, use the Affordances API. This is useful for defining custom payloads and query parameters:

    // Start from a Link instance
    Affordances affordances = Affordances.of(link);
    
    affordances
        .withHttpMethod(HttpMethod.PUT)
        .withPayloadDescription(Employee.class)
        .withName("update")
        .withQueryParameter("QueryParameter");
  6. Build links in Spring MVC with WebMvcLinkBuilder

    main

    To avoid brittle URI string concatenation, use WebMvcLinkBuilder to create links by pointing to controller classes. This approach uses Spring's ServletUriComponentsBuilder to automatically resolve the base URI (protocol, host, port, etc.) from the current request.

    You can create a link to a collection resource by referencing the controller class and specifying a relation:

    import static org.sfw.hateoas.server.mvc.WebMvcLinkBuilder.*;
    
    Link link = linkTo(PersonController.class).withRel("people");

    You can build nested paths using .slash() and create self-referencing links using .withSelfRel():

    Person person = new Person(1L, "Dave", "Matthews");
    Link link = linkTo(PersonController.class).slash(person.getId()).withSelfRel();

    Creating URIs for Headers

    WebMvcLinkBuilder can also produce URI instances, which is useful for setting Location headers in responses:

    HttpHeaders headers = new HttpHeaders();
    headers.setLocation(linkTo(PersonController.class).slash(person).toUri());
    
    return new ResponseEntity<PersonModel>(headers, HttpStatus.CREATED);
    import static org.sfw.hateoas.server.mvc.WebMvcLinkBuilder.*;
    
    Link link = linkTo(PersonController.class).withRel("people");
  7. Enable HAL-FORMS media type

    main

    To enable HAL-FORMS support (which adds runtime FORM support to HAL), you must include the appropriate configuration in your application. When a client requests application/prs.hal-forms+json, the server will respond with HAL-FORMS documents containing _templates describing available operations.

    // Example of an application with HAL-FORMS enabled
    @Configuration
    @EnableHypermediaSupport(type = HypermediaType.HAL_FORMS)
    public class HalFormsApplication {
      // ...
    }
  8. Internationalize HAL link titles

    main

    HAL supports a title attribute for link objects. Spring HATEOAS automatically populates these using Spring's resource bundle abstraction.

    To define titles, create a resource bundle named rest-messages and use the key template _links.$relationName.title.

    # rest-messages.properties
    _links.cancel.title=Cancel order
    _links.payment.title=Proceed to checkout
  9. Activate hypermedia support using `@EnableHypermediaSupport`

    main

    To ensure that RepresentationModel subtypes (like EntityModel and CollectionModel) are rendered according to specific hypermedia formats (such as HAL), use the @EnableHypermediaSupport annotation.

    When you apply this annotation, Spring HATEOAS performs the following automatic configurations:

    • Jackson Integration: Registers the necessary Jackson modules to render models in the chosen hypermedia format.
    • Link Discovery: If JSONPath is on the classpath, it registers a LinkDiscoverer to look up links by rel in plain JSON.
    • Entity Links: Enables entity links by default and bundles all EntityLinks implementations into a DelegatingEntityLinks instance available for autowiring.
    • Relation Providers: Bundles all RelProvider implementations into a DelegatingRelProvider for autowiring. This includes support for @Relation on domain types and Spring MVC controllers. If EVO inflector is on the classpath, it uses that library to pluralize collection rel values.
    @EnableHypermediaSupport(type = HypermediaType.HAL)
    class MyHypermediaConfiguration {
        // Configuration details
    }
  10. Configure WebTestClient for hypermedia testing

    main

    When testing hypermedia-enabled APIs with WebTestClient, you must apply hypermedia support to the client instance. Note that WebTestClient is immutable, so you must capture the returned mutated instance.

    Spring Boot Integration

    In a @SpringBootTest with @AutoConfigureWebTestClient, autowire both the WebTestClient.Builder and the HypermediaWebTestClientConfigurer. Use the configurer to mutate the builder before calling .build().

    @SpringBootTest
    @AutoConfigureWebTestClient
    class WebClientBasedTests {
    
    @Test
        void exampleTest(@Autowired WebTestClient.Builder builder, @Autowired HypermediaWebTestClientConfigurer configurer) {
            client = builder.apply(configurer).build();
    
            client.get().uri("/")
                    .exchange()
                    .expectBody(new TypeReferences.EntityModelType<Employee>() {})
                    .consumeWith(result -> {
                        // assert against this EntityModel<Employee>!
                    });
        }
    }