Contenido
Introducción
Hypermedia es la interrelación entre recursos mediante enlaces. La idea es sólo conocer un recurso principal y de ahí descubrir como acceder al resto de los recursos. Esto permite aumentar la interoperabilidad entre recursos con mucha menos información.
Por ejemplo:
Tenemos la tabla persona donde podemos consultar edad, nombre y sexo. En otra tabla grupo se almacena el grupo al que pertenece la persona. En otra tabla país se almacena el país al que pertenece la persona. Si necesitamos consultar los datos, grupo y país de una persona tendríamos que consumir los recursos,
http://localhost:8080/personah/3
De los resultados obtenemos el id del grupo. Para conocer el nombre del grupo,
http://localhost:8080/grupoh/2
De los resultados obtenemos el id del país. Para conocer el nombre del país,
http://localhost:8080/paish/2
Usando hypermedia podríamos presentar:
Y navegaríamos por los link para obtener toda la información requerida sin necesidad de conocer cada uno de los recursos.
Programar hypermedia
Crear Entity
Creamos los entity PersonaHateoas, GrupoHateoas y PaisHateoas.
PersonaHateoas
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
package com.mio.administrar.entity; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; @Entity public class PersonaHateoas implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private int idPersona; private int idGrupo; private int idPais; private int edad; private String nombre; private String sexo; @ManyToOne @JoinColumn(name="idGrupo",insertable=false,updatable=false) private GrupoHateoas grupoHateoas; @ManyToOne @JoinColumn(name="idPais",insertable=false,updatable=false) private PaisHateoas paisHateoas; public PersonaHateoas() { } public int getIdPersona() { return this.idPersona; } public void setIdPersona(int idPersona) { this.idPersona = idPersona; } public int getEdad() { return this.edad; } public void setEdad(int edad) { this.edad = edad; } public String getNombre() { return this.nombre; } public void setNombre(String nombre) { this.nombre = nombre; } public String getSexo() { return this.sexo; } public void setSexo(String sexo) { this.sexo = sexo; } public int getIdGrupo() { return idGrupo; } public void setIdGrupo(int idGrupo) { this.idGrupo = idGrupo; } public int getIdPais() { return idPais; } public void setIdPais(int idPais) { this.idPais = idPais; } } |
GrupoHateoas
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
package com.mio.administrar.entity; import java.io.Serializable; import java.util.List; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Table; @Entity @Table(name="grupoHateoas") public class GrupoHateoas implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private int idGrupo; private String nombre; @OneToMany(mappedBy="grupoHateoas") private List<PersonaHateoas> personaHateoas; public GrupoHateoas() { } public String getNombre() { return nombre; } public void setNombre(String nombre) { this.nombre = nombre; } public List<PersonaHateoas> getPersonaHateoas() { return this.personaHateoas; } } |
PaisHateoas
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
package com.mio.administrar.entity; import java.io.Serializable; import java.util.List; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Table; @Entity @Table(name="paisHateoas") public class PaisHateoas implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private int idPais; private String nombre; @OneToMany(mappedBy="paisHateoas") private List<PersonaHateoas> personaHateoas; public PaisHateoas() { } public String getNombre() { return nombre; } public void setNombre(String nombre) { this.nombre = nombre; } public List<PersonaHateoas> getPersonaHateoas() { return this.personaHateoas; } } |
Crear Repository
Creamos los repository PersonaHateoasRepository, GrupoHateoasRepository y PaisHateoasRepository.
PersonaHateoasRepository
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.mio.administrar.repository; import java.util.List; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.transaction.annotation.Transactional; import com.mio.administrar.entity.PersonaHateoas; @Transactional public interface PersonaHateoasRepository extends CrudRepository<PersonaHateoas, Long>{ @Query("select p from PersonaHateoas p where p.idPersona = ?1") PersonaHateoas unaPersona(int id); @Query("select p from PersonaHateoas p") List<PersonaHateoas> allPersonas(); } |
GrupoHateoasRepository
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.mio.administrar.repository; import java.util.List; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.transaction.annotation.Transactional; import com.mio.administrar.entity.GrupoHateoas; @Transactional public interface GrupoHateoasRepository extends CrudRepository<GrupoHateoas, Long>{ @Query("select g from GrupoHateoas g where g.idGrupo = ?1") GrupoHateoas unGrupo(int id); @Query("select g from GrupoHateoas g") List<GrupoHateoas> allGrupos(); } |
PaisHateoasRepository
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.mio.administrar.repository; import java.util.List; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.transaction.annotation.Transactional; import com.mio.administrar.entity.PaisHateoas; @Transactional public interface PaisHateoasRepository extends CrudRepository<PaisHateoas, Long>{ @Query("select p from PaisHateoas p where p.idPais = ?1") PaisHateoas unPais(int id); @Query("select p from PaisHateoas p") List<PaisHateoas> allPaises(); } |
Crear Controller
Creamos los repository PersonaHateoasController, GrupoHateoasController y PaisHateoasController.
PersonaHateoasController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
package com.mio.administrar.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.Link; import org.springframework.hateoas.Resources; import org.springframework.hateoas.Resource; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.mio.administrar.entity.PersonaHateoas; import com.mio.administrar.repository.PersonaHateoasRepository; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*; import java.util.List; import java.util.stream.Collectors; @RestController @RequestMapping(path="/personah", produces = "application/hal+json") public class PersonaHateoasController { @Autowired private PersonaHateoasRepository personaHateoasRepository; /** * Obtiene todas las personas: GET http://{servidor}:{puerto}/personah * @return Lista de personas */ @RequestMapping(method = RequestMethod.GET) public Resources<Resource<PersonaHateoas>> allPersonas() { List<PersonaHateoas> listPersonaHateoas = personaHateoasRepository.allPersonas(); return personaToResource(listPersonaHateoas); } /** * Obtiene una person por id: GET http://{servidor}:{puerto}/personah/{id} * @param id Id de la persona. En la Url * @return Una persona */ @RequestMapping(value = "/{id}", method = RequestMethod.GET) public Resource<PersonaHateoas> unaPersona(@PathVariable int id) { PersonaHateoas personaHateoas = personaHateoasRepository.unaPersona(id); return personaToResource(personaHateoas); } /** * Recorre una lista de Personas para agregar links * @param personas Lista de personas * @return Resources */ private Resources<Resource<PersonaHateoas>> personaToResource(List<PersonaHateoas> personas) { Link selfLink = linkTo(methodOn(PersonaHateoasController.class).allPersonas()).withSelfRel(); List<Resource<PersonaHateoas>> customerResources = personas.stream().map(persona -> personaToResource(persona)).collect(Collectors.toList()); return new Resources<>(customerResources, selfLink); } /** * Agrega links a Una persona * @param persona Una persona * @return Resource */ private Resource<PersonaHateoas> personaToResource(PersonaHateoas persona) { Link selfLink = linkTo(methodOn(PersonaHateoasController.class).unaPersona(persona.getIdPersona())).withSelfRel(); Link grupoLink = linkTo(methodOn(GrupoHateoasController.class).unGrupo(persona.getIdGrupo() )).withRel("Grupo"); Link paisLink = linkTo(methodOn(PaisHateoasController.class).unPais(persona.getIdPais() )).withRel("Pais"); return new Resource<>(persona, selfLink,grupoLink,paisLink); } } |
GrupoHateoasController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
package com.mio.administrar.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.mio.administrar.entity.GrupoHateoas; import com.mio.administrar.repository.GrupoHateoasRepository; @RestController @RequestMapping(path="/grupoh", produces = "application/hal+json") public class GrupoHateoasController { @Autowired private GrupoHateoasRepository grupoHateoasRepository; /** * Obtiene todos los grupos: GET http://{servidor}:{puerto}/grupoh * @return Lista de grupos */ @RequestMapping(method = RequestMethod.GET) public Iterable<GrupoHateoas> findAll() { return grupoHateoasRepository.findAll() ; } /** * Obtiene un grupo por id: GET http://{servidor}:{puerto}/grupoh/{id} * @param id Id del grupo. En la Url * @return Un grupo */ @RequestMapping(value = "/{id}", method = RequestMethod.GET) public GrupoHateoas unGrupo(@PathVariable int id) { return grupoHateoasRepository.unGrupo(id); } } |
PaisHateoasController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
package com.mio.administrar.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.mio.administrar.entity.PaisHateoas; import com.mio.administrar.repository.PaisHateoasRepository; @RestController @RequestMapping(path="/paish", produces = "application/hal+json") public class PaisHateoasController { @Autowired private PaisHateoasRepository paisHateoasRepository; /** * Obtiene todos los paises: GET http://{servidor}:{puerto}/paish * @return Lista de paises */ @RequestMapping(method = RequestMethod.GET) public Iterable<PaisHateoas> findAll() { return paisHateoasRepository.findAll() ; } /** * Obtiene un pais por id: GET http://{servidor}:{puerto}/paish/{id} * @param id Id del pais. En la Url * @return Un pais */ @RequestMapping(value = "/{id}", method = RequestMethod.GET) public PaisHateoas unPais(@PathVariable int id) { return paisHateoasRepository.unPais(id); } } |
Explicación de PersonaHateoasController
Todas las demás clases ya las conocemos, solo vamos a explicar algunos detalles de la clase PersonaHateoasController.
- Hateoas (Hypermedia As The Engine Of Application State). Aunque en nuestros ejemplos estamos modelando tablas de una Base de Datos, normalmente conocido como CRUD (Create Read Update and Delete), lo que estamos haciendo es exponer la capa de datos y dejando que el consumidor interprete e implemente la lógica del negocio. Siguiendo la filosofía de REST deberíamos de exponer los distintos casos de uso del sistema y no solo una simple interfaz. La intención de Hateoas es alejarse de CRUD y basarse en un enfoque hypermedia.
- application/hal+json. Existen diferentes formas de exponer los recursos en hypermedia, uno de los más extendidos es HAL (Hypertext Application Language). Es una extensión de json con la capacidad de definir enlaces y controles. application/hal+json es el tipo mime para HAL.
- Método personaToResource con parámetro PersonaHateoas. Crea los link y los retorna como un Resource (Recurso).
- Método personaToResource con parámetro List<PersonaHateoas>. Recorre la lista de PersonaHateoas para cargar los Recursos.
Resultado
Al ejecutar
http://localhost:8080/personah
El resultado es:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
{ "_embedded": { "personaHateoasList": [ { "idPersona": 1, "idGrupo": 1, "idPais": 2, "edad": 2, "nombre": "Juan", "sexo": "M", "_links": { "self": { "href": "http://localhost:8080/personah/1" }, "Grupo": { "href": "http://localhost:8080/grupoh/1" }, "Pais": { "href": "http://localhost:8080/paish/2" } } }, { "idPersona": 2, "idGrupo": 2, "idPais": 3, "edad": 10, "nombre": "Linda", "sexo": "F", "_links": { "self": { "href": "http://localhost:8080/personah/2" }, "Grupo": { "href": "http://localhost:8080/grupoh/2" }, "Pais": { "href": "http://localhost:8080/paish/3" } } }, { "idPersona": 3, "idGrupo": 2, "idPais": 1, "edad": 5, "nombre": "Luisa", "sexo": "M", "_links": { "self": { "href": "http://localhost:8080/personah/3" }, "Grupo": { "href": "http://localhost:8080/grupoh/2" }, "Pais": { "href": "http://localhost:8080/paish/1" } } } ] }, "_links": { "self": { "href": "http://localhost:8080/personah" } } } |
Otros
Descargar proyecto
Se puede descargar el proyecto completo de https://github.com/arielolivagh/administrarPersona/releases/tag/v2.0
Mencionas enlaces y controles ¿Que son los controles?
Son acciones que cambian el estado del Sistema. es una operación que se realiza sobre un recurso.