Home > Software design >  Java List to Reactor Flux to Identity Map
Java List to Reactor Flux to Identity Map

Time:06-04

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 Books 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 Authors within Flux<Author> (returned by authorRepository.findAllById) are unique, and that the returned Authors 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 the Map
  • 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).

  • Related