首页 > 解决方案 > 使用 Spring 和 Reactor 的 API 组合 (BFF)

问题描述

想象一下,我有两个微服务,我想在使用 WebFlux 的 Spring REST 控制器中实现 BFF(前端后端)模式。

来自 2 个远程服务的域对象是:

public class Comment {
    private Long id;
    private String text;
    private Long authorId;
    private Long editorId;
}

public class Person {
    private Long id;
    private String firstName;
    private String lastName;
}

并且 API Composer 必须返回以下类型的对象:

public class ComposedComment {
    private String text;
    private String authorFullName;
    private String editorFullName;
}

为了简洁起见,我编写了一个控制器,它可以模拟所有服务。

@RestController
@RequestMapping("/api")
public class Controller {

    private static final List<Comment> ALL_COMMENTS = Arrays.asList(//
            new Comment(1L, "Bla bla", 1L, null), //
            new Comment(2L, "lorem ipsum", 2L, 3L), //
            new Comment(3L, "a comment", 2L, 1L));
    private static final Map<Long, Person> PERSONS;

    static {
        PERSONS = new HashMap<>();
        PERSONS.put(1L, new Person(1L, "John", "Smith"));
        PERSONS.put(2L, new Person(2L, "Paul", "Black"));
        PERSONS.put(3L, new Person(3L, "Maggie", "Green"));
    }

    private WebClient clientCommentService = WebClient.create("http://localhost:8080/api");
    private WebClient clientPersonService = WebClient.create("http://localhost:8080/api");

    @GetMapping("/composed/comments")
    public Flux<ComposedComment> getComposedComments() {
     //This is the tricky part
    }

    private String extractFullName(Map<Long, Person> map, Long personId) {
        Person person = map.get(personId);
        return person == null ? null : person.getFirstName() + " " + person.getLastName();
    }

    @GetMapping("/comments")
    public ResponseEntity<List<Comment>> getAllComments() {
        return new ResponseEntity<List<Comment>>(ALL_COMMENTS, HttpStatus.OK);
    }

    @GetMapping("/persons/{personIds}")
    public ResponseEntity<List<Person>> getPersonsByIdIn(@PathVariable("personIds") Set<Long> personIds) {
        List<Person> persons = personIds.stream().map(id -> PERSONS.get(id)).filter(person -> person != null)
                .collect(Collectors.toList());
        return new ResponseEntity<List<Person>>(persons, HttpStatus.OK);
    }
}

我的问题是我刚刚开始使用 Reactor,我不太确定自己在做什么。这是我的 composer 方法的当前版本:

@GetMapping("/composed/comments")
public Flux<ComposedComment> getComposedComments() {
    Flux<Comment> commentFlux = clientCommentService.get().uri("/comments").retrieve().bodyToFlux(Comment.class);
    Set<Long> personIds = commentFlux.toStream().map(comment -> Arrays.asList(comment.getAuthorId(), comment.getEditorId())).flatMap(Collection::stream).filter(Objects::nonNull).collect(Collectors.toSet());
    Map<Long, Person> personsById = clientPersonService.get().uri("/persons/{ids}", personIds.stream().map(Object::toString).collect(Collectors.joining(","))).retrieve().bodyToFlux(Person.class).collectMap(Person::getId).block();
    return commentFlux.map(
            comment -> new ComposedComment(
                    comment.getText(),
                    extractFullName(personsById, comment.getAuthorId()),
                    extractFullName(personsById, comment.getEditorId()))
    );
}

它可以工作,但是我知道我应该使用 map、flatMap 和 zip 进行几次转换,而不是调用 block() 和 toStream()...你能帮我正确地重写这个方法吗?:)

标签: springreactive-programmingspring-webfluxproject-reactor

解决方案


您应该尝试使用zip运算符组成两个发布者。如果您想退回它,请不要订阅通量。

如果您不能使用 ,zip因为第二个发布者依赖于第一个发布者的结果,请使用flatMap.

您可以像这样使用 flatMap:

commentsFlux.flatMap(comment -> personService.getPersonsByIds(comment.getPersonId1() + "," + comment.getPersonId2())
                                      //at this moment you have scope on both
                                      .map(listOfTwoPersons -> new Composed(listOfTwoPersons, comment))

注意我没有使用 webflux 客户端,我只是从你的工作示例中猜测它知道包装到 Flux/Mono,即使你返回一个实体或实体列表。


推荐阅读