I'm trying to implement some simple Spring Boot GraphQL BatchMapping.
The @BatchMapping
receives a List<Book>
and then calls a @Repository
and transforms the result into a Map<Book, Author>
. I'm struggeling using Reactor's Flux
and Mono
.
@BatchMapping
Mono<Map<Book, Author>> author(List<Book> books) {
List<String> authorIds = books.stream().map(book -> book.authorId).toList();
Flux<Author> authors = authorRepository.findAllById(authorIds);
return authors.mapToTheBooksInTheCorrectOrder???();
}
Each Book
has a single Author
, but multiple Book
s can have the same Author
.
I'd like to return a Map
(more precisely a Mono<Map>
) where the key is each Book
and the value is the corresponding Author
.
You can assume that the Author
s within Flux<Author>
(returned by authorRepository.findAllById
) are unique, and that the returned Author
s have the same order (first occurence) as the authorId
from List<Book>
:
book1 = { id: "1", name: "A Scanner Darkly", authorId = "2" }
book2 = { id: "2", name: "High Fidelity", authorId = "1" }
book3 = { id: "3", name: "The Man In The High Castle", authorId = "2" }
will result in:
authors = [
{ id: "2", name: "Philip K. Dick" }
{ id: "1", name: "Nick Hornby" }
]
In other words:
- for each entry in
List<Book>
create a key in theMap
- and then get the matching value from the
Flux<Author>
The result of the method should be (for this example):
{
book1 : author2,
book2 : author1,
book3 : author2
}
All wrapped within a Mono<Map>
.
CodePudding user response:
If you're struggling with Flux
and Mono
, you can use the collectList()
operator to obtain a List<Author>
. For example:
return authorRepository
.findAllById(authorIds)
.collectList() // Mono<List<Author>>
.map(authors -> /* Use authors books to get a Map<Book, Author> */);
The benefit of doing so is that you can then use the map()
operator and work with a List<Author>
and List<Book>
, which are types you're probably more familiar with.
Personally, I would even suggest using the collectMap()
operator and create a Map
with the ID of the author as the key and the Author
as the value of the Map
. This makes it easier to retrieve the relevant Author
object for each Book
, because then you can use authorIdMap.get(book.getAuthorId())
. For example:
return authorRepository
.findAllById(authorIds)
.collectMap(Author::getId) // Mono<Map<String, Author>>
.map(authorIdMap -> /* Use authorIdMap books to get a Map<Book, Author> */);
In this case, authorIdMap
would be a Map<String, Author>
.
If you don't want to use a regular for-loop/streams within the map()
function, you can also use a reactive stream for that. To do so, you first need to make a Flux<Book>
, and then you can use the collectMap()
operator a second time, but this time the key will be the Book
object, and the value will be the corresponding Author
object.
return authorRepository
.findAllById(authorIds)
.collectMap(Author::getId) // Mono<Map<String, Author>>
.flatMap(authorIdMap -> Flux
.just(books) // Flux<Book>
.collectMap(identity(), book -> authorIdMap.get(book.getAuthorId()))); // Mono<Map<Book, Author>>
In this case, you need to use flatMap()
in stead of map()
because the function will return a reactive stream in stead of a plain object.
Also, to use the current object as the key within the collectMap()
operator, you could write collectMap(book -> book, ...)
. However, a cleaner way of writing these x -> x
lambdas is by using Function.identity()
(or identity()
if you use a static import).