Spring HATEOAS: servicios web RESTful impulsados 鈥嬧媝or hipermedia

    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.

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *