Для нового проекта я создаю rest api, который ссылается на ресурсы из второй службы. Для удобства клиента я хочу добавить эту ассоциацию, которая будет сериализована как запись _embedded.
Это вообще возможно? Я подумал о создании поддельного CrudRepository (фасад для поддельного клиента) и вручную изменить все URL-адреса для этого поддельного ресурса с помощью процессоров ресурсов. это сработает?
немного углубимся в функциональность spring-data-rest:
Data-Rest оборачивает все сущности в объекты PersistentEntityResource
, которые расширяют интерфейс Resource<T>
, предоставляемый Spring HATEOAS. Эта конкретная реализация имеет список встроенных объектов, которые будут сериализованы как поле _embedded
.
Таким образом, теоретически решение моей проблемы должно быть таким же простым, как реализация ResourceProcessor<Resource<MyType>>
и добавление моего эталонного объекта к встраиванию.
На практике у этого подхода есть некоторые уродливые, но решаемые проблемы:
PersistentEntityResource
не является универсальным, поэтому, хотя вы можете создать для него ResourceProcessor, этот процессор по умолчанию будет улавливать все. Я не знаю, что произойдет, когда вы начнете использовать Projection. Так что это не решение.
PersistentEntityResource
реализует Resource<Object>
и в результате не может быть преобразован в Resource<MyType>
и наоборот. Если вы хотите получить доступ к встроенному полю, все преобразования должны выполняться с помощью PersistentEntityResource.class.cast()
и Resource.class.cast()
.
В целом мое решение простое, эффективное и не очень красивое. Я надеюсь, что Spring-Hateoas получит полноценную поддержку HAL в будущем.
Вот мой ResourceProcessor в качестве образца:
@Bean
public ResourceProcessor<Resource<MyType>> typeProcessorToAddReference() {
// DO NOT REPLACE WITH LAMBDA!!!
return new ResourceProcessor<>() {
@Override
public Resource<MyType> process(Resource<MyType> resource) {
try {
// XXX all resources here are PersistentEntityResource instances, but they can't be cast normaly
PersistentEntityResource halResource = PersistentEntityResource.class.cast(resource);
List<EmbeddedWrapper> embedded = Lists.newArrayList(halResource.getEmbeddeds());
ReferenceObject reference = spineClient.findReferenceById(resource.getContent().getReferenceId());
embedded.add(embeddedWrappers.wrap(reference, "reference-relation"));
// XXX all resources here are PersistentEntityResource instances, but they can't be cast normaly
resource = Resource.class.cast(PersistentEntityResource.build(halResource.getContent(), halResource.getPersistentEntity())
.withEmbedded(embedded).withLinks(halResource.getLinks()).build());
} catch (Exception e) {
log.error("Something went wrong", e);
// swallow
}
return resource;
}
};
}
Если вы хотите работать с типобезопасностью и только со ссылками (дополнительные ссылки на пользовательские методы контроллера), вы можете найти вдохновение в этом образце кода:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.server.RepresentationModelProcessor;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
@Configuration
public class MyTypeLinkConfiguration {
public static class MyType {}
@Bean
public RepresentationModelProcessor<EntityModel<MyType>> MyTypeProcessorAddLifecycleLinks(MyTypeLifecycleStates myTypeLifecycleStates) {
// WARNING, no lambda can be passed here, because type is crucial for applying this bean processor.
return new RepresentationModelProcessor<EntityModel<MyType>>() {
@Override
public EntityModel<MyType> process(EntityModel<MyType> resource) {
// add custom export link for single MyType
myTypeLifecycleStates
.listReachableStates(resource.getContent().getState())
.forEach(reachableState -> {
try {
// for each possible next state, generate its relation which will get us to given state
switch (reachableState) {
case DRAFT:
resource.add(linkTo(methodOn(MyTypeLifecycleController.class).requestRework(resource.getContent().getId(), null)).withRel("requestRework"));
break;
case IN_REVIEW:
resource.add(linkTo(methodOn(MyTypeLifecycleController.class).requestReview(resource.getContent().getId(), null)).withRel("requestReview"));
break;
default:
throw new RuntimeException("Link for target state " + reachableState + " is not implemented!");
}
} catch (Exception ex) {
// swallowed
log.error("error while adding lifecycle link for target state " + reachableState + "! ex = " + ex.getMessage(), ex);
}
});
return resource;
}
};
}
}
Обратите внимание, что myTypeLifecycleStates
- это компонент "сервис" / "бизнес-логика" с автоматическим подключением.