В этом сообщении в блоге описывается концепция добавления ссылки на 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 finalvoid 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”