spring - 使用 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()...你能帮我正确地重写这个方法吗?:)
解决方案
您应该尝试使用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,即使你返回一个实体或实体列表。
推荐阅读
- go - cgo如何抑制警告
- android - SeekbarPreference 中的边距
- apache - spring boot 应用程序与 apache 负载平衡
- c# - Moq - 无法从 IGenericRepository 转换为 Generic Repository
- laravel-5 - Laravel 5.7 + 字体真棒
- c# - 循环通过设置列号(3)但长度可变的文本文档
- c# - C# 正则表达式解析有时在数字旁边包含额外空格的字符串
- python - 带有 JSon 的 GraphQL 和 Python - 未转义字符的语法错误
- python - pywinauto 没有连接到 After Effects?
- vb.net - 当 Combox 指示器选择为 yes 时复选框启用(vb.net)