-
Notifications
You must be signed in to change notification settings - Fork 328
Description
Hello,
I am migrating my GraphQL project to use spring-graphql and I noticed that my Controllers are called sequentially instead of parallel.
All the code I will paste here is a similar code, but with the exact same coding structure:
schema.graphqls
type Query {
books(bookIds: Int): [Book]
}
type Book {
bookId: Int
name: String
pageCount: Int
author: Author _#this field could be resolved in parallel with qtySold_
qtySold: Int _#this field could be resolved in parallel with author_
}
type Author {
authorId: Int
firstName: String
lastName: String
}
BookController.java
@Controller
public class BookController {
private final BookService service;
public BookController(BookService service) {
this.service = service;
}
@QueryMapping
public CompletableFuture<List<Book>> books(@Argument List<Integer> bookIds) {
return CompletableFuture.supplyAsync(() -> service.getBooksByIds(bookIds));
}
}
BookService.java
@Service
public class BookService {
private final BookRepository repository;
public BookService(BookRepository repository) {
this.repository = repository;
}
public List<Book> getBooksByIds(List<Integer> bookIds) {
return repository.findAllByIdIn(bookIds);
}
}
AuthorController.java
@Controller
public class AuthorController {
private final AuthorService service;
public AuthorController(AuthorService service, BatchLoaderRegistry batchLoaderRegistry) {
this.service = service;
batchLoaderRegistry
.forTypePair(Book.class, Author.class)
.registerMappedBatchLoader(
(books, env) -> {
return Mono.just(service.getAuthors(books));
}
);
}
@SchemaMapping(typeName = "Book", field = "author")
public CompletableFuture<Author> books(Book book, DataLoader<Book, Author> dataLoader) {
return dataLoader.load(book);
}
}
AuthorService.java
@Service
public class AuthorService {
private final AuthorRepository repository;
public AuthorService(AuthorRepository repository) {
this.repository = repository;
}
public Map<Book, Author> getAuthors(Set<Book> books) {
List<Author> authors = repository.findAllByAuthorIdIn(books.stream().map(Book::authorId).collect(Collectors.toList()));
return books.stream().collect(Collectors.toMap(book -> book, book -> getAuthorForBook(book, authors)));
}
private Author getAuthorForBook(Book book, List<Author> authors) {
return authors.stream().filter(author -> author.id() == book.authorId()).findFirst().orElse(null);
}
}
SellingController.java
@Controller
public class SellingController {
private final SellingService service;
public SellingController(SellingService service, BatchLoaderRegistry batchLoaderRegistry) {
this.service = service;
batchLoaderRegistry
.forTypePair(Book.class, Integer.class)
.registerMappedBatchLoader(
(books, env) -> {
return Mono.just(service.getSellings(books));
}
);
}
@SchemaMapping(typeName = "Book", field = "qtySold")
public CompletableFuture<Integer> qtySold(Book book, DataLoader<Book, Integer> dataLoader) {
return dataLoader.load(book);
}
}
SellingService.java
@Service
public class SellingService {
private final SellingRepository repository;
public SellingService(SellingRepository repository) {
this.repository = repository;
}
public Map<Book, Integer> getSellings(Set<Book> books) {
List<Selling> sellings = repository.findAllByBookIdIn(books.stream().map(Book::bookId).collect(Collectors.toList()));
return books.stream().collect(Collectors.toMap(book -> book, book -> getSellingQtyForBook(book, sellings).qtySold()));
}
private Selling getSellingQtyForBook(Book book, List<Selling> sellings) {
return sellings.stream().filter(selling -> selling.bookId() == book.bookId()).findFirst().orElse(new Selling(book.bookId(), 0));
On my understanding as soon as I have all Books solved GraphQL would trigger to retrieve both author and qtySold to be executed in the same time in different Threads as they rely only on Book's.
What I noticed is that both author and qtySold Controllers/Services are triggered sequentially on the same VirtualThread.
I also tried using an AsyncConfiguration (setting Core and Max PoolSize and also QueueCapacity) but I always get the same.
Just remember: this is a translated sample from the project I'm developing. The entire entities structure is way bigger and when I migrated to spring-grapql from graphql-java-kickstart one query that took from 3 to 5 seconds now is taking from 15 to 20+ seconds.
I am using
- Java 21
- SpringBoot 2.7.18
- Spring Framework 5.5.8