Spring-Data-Rest 能否处理与其他微服务资源的关联?

Can Spring-Data-Rest handle associations to Resources on other Microservices?

对于一个新项目,我正在构建一个 rest api,它引用第二个服务的资源。为了方便客户,我想添加此关联以序列化为 _embedded 条目。

这可能吗?我考虑过构建一个假的 CrudRepository(假客户端的门面)并使用资源处理器手动更改该假资源的所有 url。那行得通吗?

深入了解 spring-data-rest 的功能:

Data-Rest 将所有实体包装到 PersistentEntityResource 对象中,这些对象扩展了 spring HATEOAS 提供的 Resource<T> 接口。这个特定的实现有一个嵌入对象的列表,这些对象将被序列化为 _embedded 字段。

所以理论上我的问题的解决方案应该像实施 ResourceProcessor<Resource<MyType>> 并将我的参考对象添加到嵌入一样简单。

在实践中,这种方法有一些丑陋但可以解决的问题:

PersistentEntityResource 不是通用的,因此虽然您可以为其构建 ResourceProcessor,但该处理器默认情况下会捕获所有内容。我不确定当您开始使用投影时会发生什么。所以这不是解决方案。

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 是自动装配的 "service"/"business logic" bean。