Spring HATEOAS: servicios web RESTful impulsados ​​por hipermedia

S

Introducción

Las API REST son flexibles y permiten a los desarrolladores crear sistemas desacoplados. Con el auge de la arquitectura de microservicios, REST ha madurado aún más, ya que los microservicios se pueden construir independientemente del lenguaje o el marco utilizado en la aplicación.

Estar “en el centro de atención”: esto significa que los nuevos tipos se derivan o se construyen alrededor de las API REST, lo que nos lleva a HATEOAS.

¿Qué es HATEOAS?

Al estar en el centro de atención, se están introduciendo diferentes técnicas de arquitectura centradas en los fundamentos de REST.

Hypermedia as the Engine of Application State (HATEOAS) es un enfoque arquitectónico para mejorar la usabilidad de las API REST para las aplicaciones que consumen las API.

El propósito principal de HATEOAS es proporcionar información adicional en las respuestas de la API REST para que los usuarios de la API puedan obtener detalles adicionales del punto final de una sola llamada. Esto permite a los usuarios construir sus sistemas con llamadas API dinámicas, pasando de un punto final a otro utilizando la información recuperada de cada llamada.

Para comprender esto mejor, eche un vistazo a la siguiente respuesta de la API:

{
    "id": 1,
    "name": "Dr. Sanders",
    "speciality": "General",
    "patientList": [
        {
            "id": 1,
            "name": "J. Smalling",
            "_links": {
                "self": {
                    "href": "http://localhost:8080/patients/1"
                }
            }
        }
    ],
    "_links": {
        "self": {
            "href": "http://localhost:8080/doctors/1"
        },
        "patientList": {
            "href": "http://localhost:8080/doctors/1/patients"
        }
    }
}

Además de obtener los detalles sobre el médico, la respuesta de la API también proporciona información adicional en forma de enlaces. Por ejemplo, también se adjunta un enlace para buscar a todos los pacientes de un solo médico.

Lo que tenemos aquí es una respuesta enriquecida en recursos, donde los enlaces proporcionados son recursos que enriquecen nuestra respuesta con información adicional.

Spring HATEOAS

Spring HATEOAS proporciona bibliotecas para implementar la arquitectura HATEOAS en una aplicación Spring con facilidad. Usando la API de Spring HATEOAS, los enlaces se pueden crear y devolver como parte del objeto de respuesta de la API.

Dependencias de Spring HATEOAS

Usando Maven, agregar Spring HATEOAS es tan fácil como incluir las dependencias:

  org.springframework.plugin   spring-plugin-core  [2.0.0.RELEASE,)</version>
</dependency>
<dependency>
    <groupId>org.springframework.hateoas</groupId>
    <artifactId>spring-hateoas</artifactId>
    <version>[1.0.3.RELEASE,)</version>
</dependency>

Alternativamente, usando Gradle, puede agregar:

implementation 'org.springframework.plugin:spring-plugin-core:2.+'
implementation 'org.springframework.hateoas:spring-hateoas:1.+'

Spring Boot HATEOAS dependencias

Aún más fácil, para las aplicaciones Spring Boot, puede usar la spring-boot-starter-hateoasdependencia de Maven:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-hateoas</artifactId>
    <version>[2.2.4.RELEASE,)</version>
</dependency>

Del mismo modo, si está usando Gradle, simplemente puede agregar:

implementation 'org.springframework.boot:spring-boot-starter-hateoas:2.+'

El uso de spring-boot-starter-hateoasdependencias incluye spring-hateoasy spring-boot-starter-webdependencias, por lo que, naturalmente, no se necesitan otros iniciadores.

Bloques de construcción Spring HATEOAS

Los bloques de construcción básicos para Spring HATEOAS son Links y RepresentationModels (un contenedor para una colección de Links).

El RepresentationModelse extiende luego en EntityModel(para recursos individuales) y CollectionModel(para múltiples recursos), así como una PagedModel.

Tomemos un breve momento para explicar cada uno de estos antes de implementarlos en una demostración de trabajo.

El Linkobjeto inmutable se usa para almacenar metadatos para un recurso (URI o ubicación), y el usuario final puede navegar a los recursos que enriquecen nuestra respuesta API. Un enlace básico con un URI de recurso podría verse así:

"_links": {
    "self": {
        "href": "http://localhost:8080/doctors/1"
    }
}

El enlace contiene un hrefatributo que apunta al URI del recurso. El hrefatributo está envuelto dentro de una selfetiqueta, que identifica la relación con la entidad. Esto significa que el recurso se apunta a sí mismo, esencialmente.

¿Por qué el recurso apunta a sí mismo?

Es posible que los recursos devueltos no sean la representación completa de sí mismos. Un médico puede tener una lista de pacientes, pero es posible que no queramos devolverla de forma predeterminada.

Si luego quisiéramos echar un vistazo a la lista de médicos, podemos navegar a través del enlace.

Modelos de representación

El RepresentationModelactúa como una clase raíz de todas las demás clases del modelo de Spring HATEOAS. Contiene una colección de Linkmensajes de correo electrónico y proporciona un método para agregarlos / eliminarlos.

Crear tu propio modelo es tan fácil como ampliar la RepresentationModelclase. De lo contrario, puede utilizar cualquiera de los modelos disponibles:

  • Modelo de entidad : EntityModelse utiliza para representar un recurso que corresponde a un solo objeto. Puede envolver su recurso con EntityModely pasarlo a un servicio de llamada o devolverlo a través de un punto final REST.
  • Modelo de colección : similar a EntityModel, CollectionModelse usa para envolver recursos; sin embargo, envuelve un recurso que corresponde a una colección de objetos.
  • Modelo paginado : Además, dado que muchos puntos finales de la API REST devuelven respuestas, que son colecciones paginables, Spring HATEOAS proporciona la forma PagedModelde representar dichos recursos.

Creemos un recurso de muestra que extendsla RepresentationModelclase:

public class Doctor extends RepresentationModel<Doctor> {
    private int id;
    private List<Patient> patientList;
}

Por ahora nuestro Doctormodelo solo tiene una idpropiedad y una lista de pacientes. A continuación, agregaremos un Linkal recurso, que apuntará el recurso a sí mismo.

Objeto de enlace

Los Linkobjetos Spring HATEOAS toman Stringargumentos para especificar el URI y la relación entre las entidades. Estos son básicamente los atributos hrefy rel:

Link selfLink = new Link("http://localhost:8080/doctors/1", "self");

Doctor doctor = new Doctor();
doctor.add(selfLink);

Cuando se devuelve un objeto doctor (como se muestra en la aplicación de demostración en secciones posteriores), el cuerpo de respuesta contendrá:

"_links": {
    "self": {
        "href": "http://localhost:8080/doctors/1"
    }
}
MVC LinkBuilder

Sin Linkembargo, no se recomienda codificar valores en el constructor de la clase. Rápidamente se vuelve difícil administrarlos y actualizarlos a medida que crece su aplicación / API. Para combatir esto, podemos usar el WebMvcLinkBuilder, que nos permite crear enlaces usando clases de controlador y apuntando a sus métodos.

Recreemos el enlace del ejemplo anterior usando WebMvcLinkBuilder:

Link link = linkTo(methodOn(DoctorController.class).getDoctorById(id)).withSelfRel();

Aquí, usamos el enfoque más programático para crear vínculos. Apunta al getDoctorById()método dentro de la DoctorControllerclase. Como apunta a sí mismo, usamos el withSelfRel()método para especificar la relación.

Alternativamente, podríamos haber usado el withRel()método y pasado un String con una relación diferente.

Spring HATEOAS traducirá los detalles del punto final de la clase del controlador y el método que hemos proporcionado a WebMvcLinkBuilder. La salida de este Linkobjeto será exactamente la misma que la generada en el ejemplo anterior.

Para crear enlaces para recursos que tienen una relación entre ellos o apuntan a un recurso diferente, usaríamos el withRel()método. Con esto podemos especificar el punto final con el que se puede acceder al recurso vinculado:

Link link = linkTo(methodOn(DoctorController.class)
                .getDoctorPatients(doctor.getId()))
                .withRel("patientList");

El fragmento de código anterior especifica que el usuario puede obtener el patientListpara el doctorobjeto, utilizando el getDoctorPatients()método dentro de la DoctorControllerclase. Cuando se agrega al cuerpo de la respuesta, genera el siguiente enlace:

"_links": {
    "patientList": {
        "href": "http://localhost:8080/doctors/1/patients"
    }
}

Tenga en cuenta que no proporcionamos ninguna URL al crear el enlace. Spring HATEOAS puede extraer la información del creador de enlaces y generar una URL basada en las asignaciones que hemos utilizado.

Configuración

Para representar correctamente diferentes RepresentationModelsubtipos, puede habilitar la representación de hipermedios utilizando la @EnableHypermediaSupportanotación. Puede pasar el HypermediaTypecomo un argumento a esta anotación, lo que le permite especificar el tipo de hipermedia, como JSON, UBER , HAL , etc. El uso de la anotación permite a Spring configurar los módulos de Jackson necesarios para representar hipermedia correctamente.

Normalmente, Spring detectará la pila de tecnología que está utilizando y ajustará automáticamente la configuración cuando agregue la anotación. Sin embargo, si tiene algunos requisitos personalizados, le sugerimos que consulte la documentación oficial .

Aplicación de demostración

Con todo lo dicho, escribamos una aplicación Spring simple con soporte de HATEOAS yendo a Spring Initializr y generando una aplicación Spring Boot en blanco con la dependencia Spring HATEOAS( spring-boot-hateoas-starter):

Crear un recurso

Para cualquier recurso que se exponga a través de la API REST, debe extenderse RepresentationModel. Al extender la RepresentationModelclase, también heredamos el add()método, que se utiliza para adjuntar enlaces.

Creemos un modelo para Doctor:

public class Doctor extends RepresentationModel<Doctor> {
    private int id;
    private String name;
    private String speciality;
    private List<Patient> patientList;
}

Como la Doctorclase tiene una relación con los pacientes, creemos también el Patientmodelo:

public class Patient extends RepresentationModel<Patient> {
    private int id;
    private String name;
}

A continuación, en un controlador, en nuestro caso a DoctorController, conectaremos automáticamente DoctorService:

@RestController
@RequestMapping(value = "/doctors")
public class DoctorController {

    @Autowired
    DoctorService doctorService;
}

Como es de esperar, que contiene métodos tales como getDoctor(), getDoctorWithPatients(), getDoctors(), etc, que todo cambio un Doctoro una List<Doctor>. La implementación se omite por brevedad; si desea echar un vistazo, el código está en GitHub .

Con esto, hemos creado un recurso. Al recuperar recursos, esperaremos un solo recurso o una colección de recursos. Como se indicó anteriormente, los envolveremos en un EntityModelo CollectionModel, respectivamente.

Recuperar un solo recurso

Primero implementemos la funcionalidad de buscar un solo médico. Como esperamos que la llamada a la API devuelva un solo recurso, envolveremos nuestra respuesta dentro de una EntityModelclase:

@GetMapping(value = "/{id}")
public EntityModel<Doctor> getDoctorById(@PathVariable int id) {
    Doctor doctor = doctorService.getDoctorWithPatients(id);

    for (final Patient patient : doctor.getPatientList()) {
        Link selfLink = linkTo(methodOn(PatientController.class)
                               .getPatientById(patient.getId())).withSelfRel();
        patient.add(selfLink);
    }

    doctor.add(linkTo(methodOn(DoctorController.class)
                      .getDoctorById(id)).withSelfRel());
    doctor.add(linkTo(methodOn(DoctorController.class)
                      .getDoctorPatients(doctor.getId())).withRel("patientList"));

    return new EntityModel<>(doctor);
}

Después de recuperar el Doctorobjeto, recorremos la lista de pacientes asociados y agregamos un enlace para cada uno de ellos. Cada uno de estos enlaces se puede utilizar para que cada individuo Patientacceda a PatientController.

De manera similar, estamos agregando un selfenlace al Doctorque se usó para realizar las llamadas a la API. Junto con el autoenlace también estamos agregando un enlace relacional, apuntando a la lista de pacientes.

Al final del método, hemos envuelto nuestro Doctorobjeto en una EntityModelclase y esto EntityModelse devuelve como respuesta:

{
    "id": 1,
    "name": "Dr. Sanders",
    "speciality": "General",
    "patientList": [
        {
            "id": 1,
            "name": "J. Smalling",
            "_links": {
                "self": {
                    "href": "http://localhost:8080/patients/1"
                }
            }
        },
        {
            "id": 2,
            "name": "Samantha Williams",
            "_links": {
                "self": {
                    "href": "http://localhost:8080/patients/2"
                }
            }
        }
    ], "_links": {"self": {"href": "http: // localhost: 8080 / Doctors / 1"}, "PatientList": {"href": "http: // localhost: 8080 / Doctors / 1 / pacientes "}}}

“Dr. Sanders” tiene a “J. Smalling” y “Samantha Williams” como sus pacientes, y tanto el criterio de valoración del médico como el criterio de valoración de una lista de pacientes del médico se agregan a la respuesta, lo que la enriquece con recursos.

Recuperar varios recursos

Creemos otra llamada GET que devuelva todos los médicos disponibles en el sistema. Ahora que la respuesta que esperamos será una colección de Doctor objetos, envolveremos la respuesta dentro del CollectionModel:

@GetMapping
public CollectionModel<Doctor> getDoctors() {
    List<Doctor> doctors = doctorService.getDoctorsWithPatients();

    for (final Doctor doctor : doctors) {
        doctor.add(linkTo(methodOn(DoctorController.class)
                          .getDoctorById(doctor.getId())).withSelfRel());
        doctor.add(linkTo(methodOn(DoctorController.class)
                          .getDoctorPatients(doctor.getId())).withRel("patientList"));

        for (final Patient patient : doctor.getPatientList()) {
            Link selfLink = linkTo(methodOn(PatientController.class)
                                   .getPatientById(patient.getId())).withSelfRel();
            patient.add(selfLink);
        }
    }

    Link link = linkTo(methodOn(DoctorController.class).getDoctors()).withSelfRel();

    return new CollectionModel<>(doctors, link);
}

En este método, junto con el self enlace para la llamada REST en sí, también estamos agregando un enlace propio para recuperar a cada médico individual. Cada médico tiene un vínculo relacional, que apunta a los pacientes asociados. Dentro de la lista de pacientes, cada paciente también tiene un self enlace, que también se puede utilizar para recuperar al paciente específico.

Una vez que se agregan todos los enlaces, hemos envuelto la colección de Doctor objetos dentro de un CollectionModel y lo devolvió:

{
    "_embedded": {
        "doctorList": [
            {
                "id": 1,
                "name": "Dr. Sanders",
                "speciality": "General",
                "patientList": [
                    {
                        "id": 1,
                        "name": "J. Smalling",
                        "_links": {
                            "self": {
                                "href": "http://localhost:8080/patients/1"
                            }
                        }
                    }
                ],
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/doctors/1"
                    },
                    "patientList": {
                        "href": "http://localhost:8080/doctors/1/patients"
                    }
                }
            },
            {
                "id": 2,
                "name": "Dr. Goldberg",
                "speciality": "General",
                "patientList": [
                    {
                        "id": 4,
                        "name": "K. Oliver",
                        "_links": {
                            "self": {
                                "href": "http://localhost:8080/patients/4"
                            }
                        }
                    }
                ],
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/doctors/2"
                    },
                    "patientList": {
                        "href": "http://localhost:8080/doctors/2/patients"
                    }
                }
            }
        ]
    },
    "_links": {
        "self": {
            "href": "http://localhost:8080/doctors"
        }
    }
}

Como puede ver en el resultado, con solo hacer una sola llamada, el usuario puede descubrir información adicional, que de otro modo no estaría presente.

Conclusión

Spring HATEOAS proporciona las bibliotecas y la infraestructura necesarias para implementar la arquitectura HATEOAS en aplicaciones basadas en Spring.

Como se desprende de los resultados, los usuarios pueden descubrir información adicional a partir de una sola llamada REST. Con esta información, es más fácil crear clientes REST dinámicos.

En este artículo, discutimos cómo funciona HATEOAS, la implementación de Spring y terminamos con la construcción de una aplicación simple para demostrar los conceptos.

El código fuente del código de muestra se puede encontrar aquí en GitHub.

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con tus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad