Рубрики
Без рубрики

Spring HATEOAS: веб-сервисы RESTful, управляемые гипермедиа

С помощью HATEOAS мы можем предоставить дополнительную информацию в одном вызове API. В этой статье мы будем создавать веб-сервис RESTful, управляемый гипермедиа, на Java и Spring HATEOAS.

Автор оригинала: Taimoor Choudhary.

Вступление

API-интерфейсы REST являются гибкими и позволяют разработчикам создавать отдельные системы. С развитием архитектуры микросервисов – REST стал еще более зрелым, поскольку микросервисы могут создаваться независимо от языка или структуры, используемой в приложении.

Быть “в центре внимания” – это означает, что новые типы создаются или создаются на основе API REST, что приводит нас к HATEOAS .

Что такое HATEOAS?

Находясь в центре внимания, внедряются различные методы архитектуры, ориентированные на основы REST.

Гипермедиа как механизм состояния приложения (HATEOAS) – это архитектурный подход для повышения удобства использования API REST для приложений, использующих API.

Основная цель HATEOAS – предоставить дополнительную информацию в ответах REST API, чтобы пользователи API могли получать дополнительные сведения о конечных точках из одного вызова. Это позволяет пользователям создавать свои системы с помощью динамических вызовов API, перемещаясь от одной конечной точки к другой, используя информацию, полученную в результате каждого вызова.

Чтобы лучше понять это, взгляните на следующий ответ 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"
        }
    }
}

Помимо получения подробной информации о враче, ответ API также предоставляет дополнительную информацию в виде ссылок. Например, прикреплена ссылка для выборки всех пациентов одного врача.

То , что мы имеем здесь,-это обогащенный ресурсами ответ , где предоставленные ссылки являются ресурсами, которые обогащают наш ответ дополнительной информацией.

Весенняя НЕНАВИСТЬ

Spring HATEOAS предоставляет библиотеки для простой реализации архитектуры HATEOAS в приложении Spring. Используя API Spring HATEOAS, ссылки могут быть созданы и возвращены как часть объекта ответа API.

Весенняя НЕНАВИСТЬ К зависимостям

Используя Maven, добавить Spring HATEOAS так же просто, как включить зависимости:


    org.springframework.plugin
    spring-plugin-core
    [2.0.0.RELEASE,)


    org.springframework.hateoas
    spring-hateoas
    [1.0.3.RELEASE,)

В качестве альтернативы, используя Gradle, вы можете добавить:

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

Зависимости Spring Boot HATEOAS

Еще проще, для приложений Spring Boot вы можете использовать зависимость spring-boot-starter-hateoas Maven:


    org.springframework.boot
    spring-boot-starter-hateoas
    [2.2.4.RELEASE,)

Аналогично, если вы используете Gradle, вы можете просто добавить:

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

Использование spring-boot-starter-hateoas зависимости включает spring-hateoas и spring-boot-starter-web зависимости, поэтому, естественно, никакие другие стартеры не требуются.

Строительные блоки Spring HATEOAS

Основными строительными блоками для Spring HATEOAS являются Link s и Модель представления s (контейнер для коллекции Link s).

Модель представления затем расширяется в Модель сущности (для отдельных ресурсов) и Модель коллекции (для нескольких ресурсов), а также Модель страницы .

Давайте уделим немного времени объяснению каждого из них, прежде чем применять их в рабочей демонстрации.

Связи

Неизменяемый объект Link используется для хранения метаданных ресурса (URI или местоположения), и конечный пользователь может перейти к ресурсам, которые обогащают наш ответ API. Базовая ссылка с URI ресурса может выглядеть следующим образом:

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

Ссылка содержит атрибут href , указывающий на URI ресурса. Атрибут href заключен в тег self , который идентифицирует связь с сущностью. Это означает, что ресурс, по сути, указывает на самого себя.

Почему ресурс указывает на себя?

Возвращенные ресурсы могут не быть полным представлением самих себя. У врача может быть список пациентов, но мы можем не захотеть возвращать его по умолчанию.

Если затем мы захотим взглянуть на список врачей, мы можем перейти к нему по ссылке.

Модели представления

Модель представления | действует как корневой класс для всех других классов моделей Spring HATEOAS. Он содержит коллекцию Ссылок и предоставляет метод для их добавления/удаления.

Создать собственную модель так же просто, как расширить класс Модель представления . В противном случае вы можете использовать любую из доступных моделей:

  • Модель сущности : Модель сущности используется для представления ресурса, соответствующего одному объекту. Вы можете обернуть свой ресурс моделью сущности и передать его вызывающей службе или вернуть его через конечную точку REST.

  • Модель коллекции : Аналогично модели Сущности , модель Коллекции используется для обертывания ресурсов, хотя она обертывает ресурс, соответствующий коллекции объектов.

  • Модель подкачки : Кроме того, поскольку многие конечные точки API REST возвращают ответы, которые являются коллекциями с возможностью просмотра страниц, Spring HATEOAS предоставляет Модель страницы для представления таких ресурсов.

Создание Ссылок

Давайте создадим образец ресурса, который расширяет Модель представления класса:

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

На данный момент у нашей модели Doctor есть только свойство id и список пациентов. Далее мы добавим Ссылку на ресурс, которая укажет ресурс на самого себя.

Объект Связи

Spring HATEOAS Ссылка объекты принимают Строку аргументы для указания URI и связи между сущностями. В основном это атрибуты href и rel :

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

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

Когда возвращается объект doctor (как показано в демонстрационном приложении в последующих разделах), тело ответа будет содержать:

"_links": {
    "self": {
        "href": "http://localhost:8080/doctors/1"
    }
}
Компоновщик MVC

Git Essentials

Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!

Однако не рекомендуется жестко кодировать значения в конструкторе класса Link . Быстро становится трудно управлять ими и обновлять их по мере роста вашего приложения/API. Для борьбы с этим мы можем использовать WebMvcLinkBuilder , который позволяет нам создавать ссылки с использованием классов controller и указывать на их методы.

Давайте воссоздадим ссылку из предыдущего примера с помощью WebMvcLinkBuilder :

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

Здесь мы используем более программный подход к созданию ссылок. Он указывает на метод get Doctor By Id() внутри класса Контроллера Doctor. Поскольку он указывает на себя, мы используем метод withSelfRel() для указания связи.

В качестве альтернативы мы могли бы использовать метод with Real() и передать строку с другим отношением.

Spring HATEOAS переведет сведения о конечной точке из класса контроллера и метода, которые мы предоставили WebMvcLinkBuilder . Выходные данные этого объекта Link будут точно такими же, как и в предыдущем примере.

Реляционные Связи

Чтобы создать ссылки для ресурсов, которые имеют связь между собой или указывают на другой ресурс, мы бы использовали метод withRel () . Используя это, мы можем указать конечную точку, с которой можно получить доступ к связанному ресурсу:

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

Приведенный выше фрагмент кода указывает, что пользователь может получить список пациентов для объекта doctor , используя метод getDoctorPatients() внутри класса DoctorController . При добавлении в тело ответа создается следующая ссылка:

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

Обратите внимание, что мы не указали никакого URL-адреса при создании ссылки. Spring HATEOAS может извлекать информацию из конструктора ссылок и генерировать URL-адрес на основе используемых нами сопоставлений.

Конфигурация

Чтобы правильно отобразить различные Модели представления подтипы, вы можете включить представление гипермедиа с помощью @EnableHypermediaSupport аннотации. Вы можете передать Тип гипермедиа в качестве аргумента этой аннотации, что позволит вам указать тип гипермедиа, например, JSON, UBER , HAL и т. Д. Использование аннотации позволяет Spring настроить необходимые модули Джексона для правильной визуализации гипермедиа.

Как правило, Spring определяет используемый вами технологический стек и автоматически корректирует конфигурацию при добавлении аннотации. Хотя, если у вас есть какие-то особые требования, мы предлагаем вам ознакомиться с официальной документацией .

Демонстрационное приложение

Учитывая все сказанное, давайте напишем простое приложение Spring с поддержкой HATEOAS, перейдя в Spring Initializr и создав пустое приложение Spring Boot с зависимостью Spring HATEOAS ( spring-boot-hateoas-starter ):

Создание ресурса

Для любого ресурса, доступ к которому должен предоставляться через REST API, необходимо расширить Модель представления . Расширяя класс Модель представления , мы также наследуем метод add () , который используется для прикрепления к нему ссылок.

Давайте создадим модель для Врача :

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

Поскольку класс Врач имеет отношения с пациентами, давайте также создадим модель Пациент :

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

Далее, в контроллере, в нашем случае Контроллере врача , мы автоматически подключим Службу врача :

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

    @Autowired
    DoctorService doctorService;
}

Как и следовало ожидать, он содержит такие методы, как получить Врача () , получить Врача с пациентами () , получить Врачей () и т.д., Которые все возвращают Врача или Список<Доктор> . Реализация опущена для краткости – если вы хотите взглянуть, код находится на GitHub .

С помощью этого мы создали ресурс. При извлечении ресурсов мы будем ожидать либо один ресурс, либо набор ресурсов. Как указывалось ранее, мы завернем их в Модель сущности или Модель коллекции соответственно.

Получение одного ресурса

Давайте сначала реализуем функциональность выбора одного врача. Поскольку мы ожидаем, что вызов API вернет один ресурс, мы завернем наш ответ в модель сущности класс:

@GetMapping(value = "/{id}")
public EntityModel 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);
}

После извлечения объекта Doctor мы просматриваем список связанных пациентов и добавляем ссылку для каждого из них. Каждая из этих ссылок может быть использована для получения каждого отдельного Пациента через Контроллер пациента .

Аналогично, мы добавляем self ссылку на Doctor , которая использовалась для выполнения вызовов API. Наряду со ссылкой на себя мы также добавляем реляционную ссылку, указывающую на список пациентов.

В конце метода мы завернули ваш Доктор объект в Модель сущности класс, и эта Модель сущности возвращается в качестве ответа:

{
    "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/patients"
        }
    }
}

“Доктор Сандерс” имеет “Дж. Смоллинг” и “Саманту Уильямс” в качестве своих пациентов, и как конечная точка для врача, так и конечная точка для списка пациентов доктора добавляются в ответ, что делает его обогащенным ресурсами .

Получение Нескольких Ресурсов

Давайте создадим еще один вызов GET, который вернет всех доступных врачей в системе. Теперь, когда ожидаемый ответ будет представлять собой набор объектов Doctor , мы поместим ответ в CollectionModel :

@GetMapping
public CollectionModel getDoctors() {
    List 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);
}

В этом методе, наряду со ссылкой self для самого вызова REST, мы также добавляем ссылку self для получения каждого отдельного врача. У каждого врача есть реляционная связь, которая указывает на связанных с ним пациентов. Внутри списка пациентов у каждого пациента также есть ссылка self , которую также можно использовать для поиска конкретного пациента.

Как только все ссылки будут добавлены, мы завернем коллекцию объектов Doctor в модель Коллекции и вернем ее:

{
    "_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"
        }
    }
}

Как вы можете видеть из выходных данных, просто сделав один звонок, пользователь может обнаружить дополнительную информацию, которой в противном случае не было бы.

Вывод

Spring HATEOAS предоставляет необходимые библиотеки и инфраструктуру для реализации архитектуры HATEOAS в приложениях на базе Spring.

Как видно из выходных данных, пользователи могут получить дополнительную информацию из одного вызова REST. Используя эту информацию, проще создавать динамические клиенты REST.

В этой статье мы обсудили, как работает HATEOAS, его реализацию Весной и закончили созданием простого приложения для демонстрации концепций.

Исходный код для примера кода можно найти здесь, на GitHub .