Imagine I have two microservices and I want to implement the BFF (Backend for the Frontend) pattern within a Spring REST controller which uses WebFlux.
The domain objects from the 2 remote services are:
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;
}
and the API Composer must return objects of following type:
public class ComposedComment {
private String text;
private String authorFullName;
private String editorFullName;
}
For the sake of semplicity I wrote a Controller which simulates all the services in one.
@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);
}
}
My problem is I have just began with Reactor and I am not really sure of what I am doing.. This is the current version of my composer method:
@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()))
);
}
It works, nevertheless I know I should make several transformations with map, flatMap and zip instead of invoking block() and toStream()... Can you please help me to rewrite this method correctly? :)
toStream
or other blocking operator. In your Spring 5 webflux environment you are never forced to block. – Daniel Jipa