Может ли spring-data-rest обрабатывать ассоциации с ресурсами на других микросервисах?

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

Это вообще возможно? Я подумал о создании поддельного CrudRepository (фасад для поддельного клиента) и вручную изменить все URL-адреса для этого поддельного ресурса с помощью процессоров ресурсов. это сработает?

0
0
164
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

немного углубимся в функциональность 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 - это компонент "сервис" / "бизнес-логика" с автоматическим подключением.

Другие вопросы по теме