Skip to content

5. Spring Hateoas

1. HATEOAS

Hateoas (Hypermedia as the engine of application state) is a RESTful API principle defined by Roy Fielding. It mainly means that, client can move by full application only from general ID URI's in hypermedia format. The principle implies that the API should guide the client through the application by returning relevant information about the next potential steps, along with each response.

For the connection between the server and the client, Fielding defines these four characteristics:

  • Unique identification of all resources: all resources must be able to be identified with a URI (Unique Resource Identifier).
  • Interaction with resources through representations: If a client needs a resource, the server sends it a representation (eg, HTML, JSON, or XML) so that the client can modify or delete the original resource.
  • Explicit messages: each message exchanged between the server and the client must contain all the data necessary to understand each other.
  • HATEOAS: This principle also integrates a REST API. This hypermedia-based structure makes it easy for customers to access the application, since they do not need to know anything else about the interface to be able to access and navigate through it.

HATEOAS is, in short, one of the most basic properties of REST APIs and as such, essential in any REST service.

A returned value without HATEOAS, with a client data:

JSON
{
    "idCliente": 3,
    "nif": "33333333C",
    "nombre": "Vicente",
    "apellidos": "Mondragón",
    "claveSeguridad": "1234",
    "email": "vicente.mondragon@tia.es",
    "recomendacion": {
      "idRecomendacion": 3,
      "observaciones": "Realiza muchos pedidos"
    },
    "listaCuentas": [
      {
        "idCuenta": 8,
        "banco": "1001",
        "sucursal": "1001",
        "dc": "11",
        "numeroCuenta": "1000000008",
        "saldoActual": 7500.0,
        "links": [

        ]
      },
      {
        "idCuenta": 10,
        "banco": "1001",
        "sucursal": "1001",
        "dc": "11",
        "numeroCuenta": "1000000010",
        "saldoActual": -3500.0,
        "links": [

        ]
      }
    ],
    "listaDirecciones": [
      {
        "idDireccion": 5,
        "descripcion": "calle de la creu, 2",
        "pais": "España",
        "cp": "46701"
      }
    ]
  }

Notice that:

  • We have got all client data
  • We don't know how to get data from specific related fields, like Direccion or Cuenta

The same request with HATEOAS:

JSON
{
  "idCliente": 3,
  "nif": "33333333C",
  "nombre": "Vicente",
  "apellidos": "Mondragón",
  "claveSeguridad": "1234",
  "email": "vicente.mondragon@tia.es",
  "links": [
    {
      "rel": "self",
      "href": "http://localhost:9090/clientes/3"
    },
    {
      "rel": "listaDirecciones",
      "href": "http://localhost:9090/clientes/3/direcciones"
    },
    {
      "rel": "listaCuentas",
      "href": "http://localhost:9090/clientes/3/cuentas"
    }
  ]
}

As you can see:

  • Only data from a client is sent
  • We have links, with clear URI's to get specific information of that client

and most important If the server changes its structure, it will send updated links, and client will work without any problems

2. Adding HATEOAS

2.1. Libraries

Note

In this text, we are going to add HATEOAS capabilities to a RESTfull api developed along the unit.

We only need to add this dependency to our pom.xml, supposing we have use a spring starter project:

XML
1
2
3
4
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>

And it is done.

2.2. Wrappers

2.2.1. Starting point

Remember that we have done to our starter project:

  • Model or DAO classes → prepared to save information in database. The are mapped with hibernate and are base for our repositories. For instance Cliente
  • DTO classes → prepared to transfer data from and into our model.
  • This class wraps the DAO (adding or removing fields).
  • These classes have method to convert from/to between DAO and DTO.
  • Is service who does the conversion
  • Client will send us information in this DTO classes
  • These classes can be use by either an API Rest or MVC web application.

2.2.2. HATEOAS wrapper

We need to define a new class to wrap our HATEOAS response.

Starting from DTO's, it contains all information from a class, own and related (Cliente plus Direccion plus Cuentas). With HATEOAS, as we showed recently, only need Cliente own information and need to generate links to related entities. Then we need to add to the client information the capability to generate and store links. The class that allow it is RepresentationModel<base_class> (full docs here). This will add to our classes:

  • Structure to store links
  • Methods to add, check and get links

The way to done it is:

Java
@Data @AllArgsConstructor
public class ClienteHATEOAS 
    extends RepresentationModel<ClienteDTO>
    implements Serializable{

    private static final long serialVersionUID = 1L;
    private long idCliente;
    private String nif;
    private String nombre;
    private String apellidos;
    private String claveSeguridad;
    private String email;

    public static ClienteHATEOAS fromClienteDTO2HATEOAS(ClienteDTO clienteDTO) {
        return new ClienteHATEOAS(
                clienteDTO.getIdCliente(),
                clienteDTO.getNif(),
                clienteDTO.getNombre(),
                clienteDTO.getApellidos(),
                clienteDTO.getClaveSeguridad(),
                clienteDTO.getEmail());
    }
} 

Important

  • As we have a base API rest working with ClienteDTO we have done this wrapper class from it.
  • Due to HATEOAS is only a response format, you can create it from Cliente as base class, but you must define your service to return Cliente too.
  • Very important to create a conversion method fromClienteDTO2HATEOAS, staying for necessary fields.

Then, we will use add(Link) method in our ClienteHATEOAS wrapper to add as many Link is necessary.

Now, the question is how to generate our Link objects in our wrapper classes. We could do it in a creative way, manipulating paths in strings and composing with complicated substring and concatenate methods.

But as we now what method is called to each reference, is better to create links obtaining references to the path from methods themselves. To do it, we must use these methods and static calls:

Java
1
2
3
4
5
6
7
import org.springframework.hateoas.Link;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;

Link self=linkTo(methodOn(controller_class.class)
            .methodName(args))
            .withSelfRel(); // or .withRel(String Link)
  • linkTo → static method who creates a Link from
  • methodOn(class) → search from a controller class for a method
  • .methodName(args) → get a real call for this method
  • And to label the link:
  • .withSelfRel() → create a link called self
  • .withRel(String Link) → create a link with given name.

Samples from our Cliente controller in next section

Java
1
2
3
4
5
ClienteDTO elCliente=clienteService.getClienteById(idCliente);

Link self=linkTo(methodOn(ClienteController.class)
            .showClienteById(elCliente.getIdCliente()))
            .withSelfRel();

This sample:

  • Loads a ClienteDTO from current ClienteService.
  • Then looks in ClienteController class for a method called showClienteById.
  • Do an internall call and search for the path and bind argument to the path (do you remember @PathVariable)
  • Finally, it gets the full path with argument and store in the Link with self reference

The result will be something like:

JSON
1
2
3
4
{
  "rel": "self",
  "href": "http://localhost:9090/clientes/3"
}
Java
1
2
3
4
5
ClienteDTO elCliente=clienteService.getClienteById(idCliente);

Link cuentas=linkTo(methodOn(CuentaController.class)
                .listCuentasCliente(elCliente.getIdCliente()))
                .withRel("listaCuentas");

This sample:

  • Loads a ClienteDTO from current ClienteService.
  • Then looks in CuentaController class for a method called listCuentasCliente.
  • Do an internall call and search for the path and bind argument to the path (do you remember @PathVariable?)
  • Finally, it gets the full path with argument and store in the Link with self reference

The result will be something like:

JSON
1
2
3
4
{
  "rel": "listaCuentas",
  "href": "http://localhost:9090/clientes/3/cuentas"
}

Once we have created links, we need to add to our last wrapper class. Simply we will use add() method to do it. In next method it receives a ClienteHATEOAS wrapper (with only data from ClienteDTO) and add as many links as we want:

Java
private void addLinks(ClienteHATEOAS elCliente) {
  // self
  Link self=linkTo(methodOn(ClienteController.class)
    .showClienteById(elCliente.getIdCliente()))
    .withSelfRel();

  elCliente.add(self);

  // direcciones
  Link direcciones=linkTo(methodOn(DireccionController.class)
      .listDireccionesCliente(elCliente.getIdCliente()))
      .withRel("listaDirecciones");
  elCliente.add(direcciones);

  //cuentas
  Link cuentas=linkTo(methodOn(CuentaController.class)
      .listCuentasCliente(elCliente.getIdCliente()))
      .withRel("listaCuentas");
  elCliente.add(cuentas);
}

The controller method to get a Cliente will be (commented):

Java
@GetMapping("/clientes/{idCliente}")
  public ResponseEntity<ClienteHATEOAS> showClienteById(@PathVariable Long idCliente){

    // get DTO from Service
    ClienteDTO elCliente=clienteService.getClienteById(idCliente);

    // if not exists return not found
    if (elCliente==null)
      return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    else {

      // create a ClienteHATEOAS from DTO calling static method
      ClienteHATEOAS returnCliente=ClienteHATEOAS.fromClienteDTO2HATEOAS(elCliente);

      // add links to ClienteHATEOAS
      addLinks(returnCliente);

      // return    
      return new ResponseEntity<>(returnCliente,HttpStatus.OK);
    }
  } 

and the result si something like:

JSON
{
  "idCliente": 3,
  "nif": "33333333C",
  "nombre": "Vicente",
  "apellidos": "Mondragón",
  "claveSeguridad": "1234",
  "email": "vicente.mondragon@tia.es",
  "links": [
    {
      "rel": "self",
      "href": "http://localhost:9090/clientes/3"
    },
    {
      "rel": "listaDirecciones",
      "href": "http://localhost:9090/clientes/3/direcciones"
    },
    {
      "rel": "listaCuentas",
      "href": "http://localhost:9090/clientes/3/cuentas"
    }
  ]
}

4. Pending work

Now, getting a simple Cliente request you have access to all information and further actions can be taken based on the metadata in the response representation.

This allows the server to change its URI scheme without breaking the client. Also, the application can advertise new capabilities by putting new links or URIs in the representation.

You are invited to complete the project develop in class adding necessary wrapper and adding HATEOAS models.