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

Добавление ссылок на REST-ресурс без уведомления ресурса

Как добавить ссылки HATEOAS на ресурс REST из модуля, о котором он не знает. С тегами java, spring, show dev.

В этом сообщении в блоге описывается концепция добавления ссылки на REST-ресурс , предоставляемый модулем A, на REST-ресурс, предоставляемый модулем B, без зависимости от A до B. В примере реализации используется Пружинный ботинок и Весенняя НЕНАВИСТЬ .

Контекст

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

REST API

Когда приложение открывает страницу сведений о пациенте, оно выполняет следующий HTTP-запрос для получения необходимых данных:

GET /patients/{patientId}

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

Например, начать новое посещение пациента можно только в том случае, если активного посещения еще нет. Таким образом, сразу после создания нового пациента ресурс пациента содержит “начать посещение” в разделе “_links”.

{
  "_id": "0a3949db-b2c4-4a8e-8ec4-5470f9a3e89e",
  "name": "John Doe",
  "address": "Guesthouse",
  "_links": {
    "self": {
      "href": "http://localhost:8080/api/patients/0a3949db-b2c4-4a8e-8ec4-5470f9a3e89e"
    },
    "start-visit": {
      "href": "http://localhost:8080/api/patients/0a3949db-b2c4-4a8e-8ec4-5470f9a3e89e/visits"
    }
  }
}

После начала посещения ссылка “начать посещение” исчезает с ресурса пациента. Но если у пациента нет открытых счетов, пациент может быть выписан по ссылке “выписка”.

{
  "_id": "0a3949db-b2c4-4a8e-8ec4-5470f9a3e89e",
  "name": "John Doe",
  "address": "Guesthouse",
  "_links": {
    "self": {
      "href": "http://localhost:8080/api/patients/0a3949db-b2c4-4a8e-8ec4-5470f9a3e89e"
    },
    "discharge": {
      "href": "http://localhost:8080/api/patients/0a3949db-b2c4-4a8e-8ec4-5470f9a3e89e/visits/4fc13f43-41db-494c-a265-aca01c3ae2a4/discharge"
    }
  }
}

Структура модуля

Этот сценарий требует взаимодействия между тремя модулями больничного приложения: управление пациентами , посещение и выставление счетов . Модуль управление пациентами заботится об основных данных пациента, модуль посещение заботится о путешествии пациента по больнице, а модуль выставление счетов заботится обо всех вещах, связанных с оплатой.

Циклические зависимости между модулями не допускаются для поддержания низкой сложности системы. Таким образом, для поддержки требований, описанных выше, модуль visit должен знать о модуле управление пациентами и модуле выставление счетов . Однако модуль управление пациентами не должен знать о модуле посещение и модуле выставление счетов .

Инверсия зависимостей

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

Таким образом, Ассемблер ресурсов Пациента запрашивает из центрального реестра, Реестра расширений ресурсов , какие ссылки следует добавить в представление ресурсов REST объекта Patient . Каждый модуль, который зависит от модуля управление пациентами , может добавлять расширения ресурсов для ресурса пациента в этот центральный реестр. Чтобы упростить этот процесс, все расширения ресурсов должны быть объявлены в классе, производном от Расширения ресурсов .

Детали реализации

Следующий фрагмент кода показывает часть Visit Module Resource Extensions , которая регистрирует функцию обратного вызова для Patient , которая вызывается всякий раз, когда запрашивается REST-ресурс для объекта Patient . Это делается с помощью метода register Link , предоставляемого базовым классом Расширения ресурсов .

@Component
public class VisitModuleResourceExtensions extends ResourceExtensions {

    // ...

    @Override
    public void init() {
        registerLink(Patient.class, patient -> createStartVisitLink(patient));
    }

    // ...
}

В рамках метода создать ссылку для начала посещения проверяется, есть ли уже пациент с активным посещением. Если да, то ссылка “начать посещение” не требуется, и метод возвращает пустой результат. Если нет, то ссылка “начать посещение” генерируется с помощью Spring HATEOAS, который может определить путь к ресурсу из соответствующего класса и метода контроллера REST.

private Optional createStartVisitLink(Patient patient) {
    if (visitRepository.hasActiveVisit(patient.getId())) {
        return Optional.empty();
    } else {
        var link = linkTo(methodOn(VisitController.class).startVisit(patient.getId())).withRel("start-visit");
        return Optional.of(link);
    }
}

Затем класс Расширения ресурсов делегирует регистрацию ссылки в Реестр расширений ресурсов . И он заботится о том, чтобы выполнить процесс регистрации во время запуска приложения с помощью аннотации Spring @PostConstruct , которая отмечает метод, который выполняется сразу после создания компонента Spring.

@RequiredArgsConstructor
public abstract class ResourceExtensions {

    private final ResourceExtensionsRegistry resourceExtensionsRegistry;

    @PostConstruct
    protected abstract void init();

    protected final  void registerLink(Class cls, Function> linkProvider) {
        resourceExtensionsRegistry.registerLink(cls, linkProvider);
    }
}

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

@Component
@RequiredArgsConstructor
class PatientResourceAssembler implements RepresentationModelAssembler {

    private final ResourceExtensionsRegistry resourceExtensionsRegistry;

    @Override
    public PatientResource toModel(Patient patient) {
        var result = PatientResource.from(patient);
        // ...
        result.add(resourceExtensionsRegistry.getLinks(Patient.class, patient));
        return result;
    }
}

Оригинал: “https://dev.to/janux_de/adding-links-to-a-rest-resource-without-the-resource-s-notice-k40”