-
Notifications
You must be signed in to change notification settings - Fork 317
Support using DataLoader from suspend function #653
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support using DataLoader from suspend function #653
Conversation
Rudimentary implementation to support returning a `CompletableFuture` from a suspend fun. `CoroutinesUtils.invokeSuspendingFunction` wraps the return value of the function in a `Mono` (or `Flux`). But it also does this when a `CompletableFuture` is returned, and thus results in a `Mono<CompletableFuture<?>>`, which isn't captured by graphql-java, and thus the dataloader is never dispatched. By unwrapping the future, and _converting_ it to a mono (as opposed to wrapping), the dataloader seems to be dispatched correctly.
Class<?> returnType = KotlinReflectionUtils.getReturnType(method); | ||
|
||
if (CompletableFuture.class.isAssignableFrom(returnType)) { | ||
@SuppressWarnings("unchecked") | ||
Mono<CompletableFuture<?>> mono = (Mono<CompletableFuture<?>>)result; | ||
// Unwrap nested CompletableFuture | ||
return mono.flatMap(Mono::fromFuture); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is inspired by the code in CoroutinesUtils
; https://github.com/spring-projects/spring-framework/blob/254fb3956727f22e8e5f4e61cd1153b1d5bff93b/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java#L112-L122
@rstoyanchev did you or anyone on the team had a chance to look at this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the ping and apologies for the delay.
Forgive the naive question, but just wondering why declare such a method that uses a DataLoader
as a coroutine? It should already be non-blocking.
The implementation looks straight forward otherwise.
Check and vote up #393 |
When first calling other suspending functions before loading data using the dataloader, e.g.; We currently use this: @SchemaMapping
fun transactions(
model: Order,
orderDataLoader: OrderDataLoader,
paymentAccountDataLoader: PaymentAccountDataLoader,
principal: Principal,
): CompletableFuture<List<OrderTransaction>> {
return CoroutineScope(Dispatchers.IO).future {
orderPaymentService.findAllByOrderId(model.id)
}.thenCompose { orderPayments ->
paymentAccountDataLoader
.loadMany(orderPayments.mapNotNull { it.paymentAccountId })
.also { paymentAccountDataLoader.dispatch() } But when we can return a dataloader from a suspend function it would look more like: @SchemaMapping
suspend fun transactions(
model: Order,
orderDataLoader: OrderDataLoader,
paymentAccountDataLoader: PaymentAccountDataLoader,
principal: Principal,
): CompletableFuture<List<OrderTransaction>> {
val orderPayments = orderPaymentService.findAllByOrderId(model.id)
return paymentAccountDataLoader
.loadMany(orderPayments.mapNotNull { it.paymentAccountId })
// Not sure if manual dispatch would still be necessary here.
.also { paymentAccountDataLoader.dispatch() }
} |
Thanks for clarifying. |
@koenpunt Add fun test() : CompletableFuture<List<OrderTransaction>> = future{
} |
That doesn't work, because This is valid code, but doesn't work because then we're back at the original problem: suspend fun test(): CompletableFuture<List<OrderTransaction>> = withContext(Dispatchers.IO) {
future {
// ...
}
} |
Rudimentary implementation to support returning a `CompletableFuture` from a suspend function. `CoroutinesUtils.invokeSuspendingFunction` wraps the return value of the function in a `Mono` (or `Flux`). But it also does this when a `CompletableFuture` is returned, and thus results in a `Mono<CompletableFuture<?>>`, which isn't captured by graphql-java, and thus the dataloader is never dispatched. By unwrapping the future, and _converting_ it to a mono (as opposed to wrapping), the dataloader is dispatched correctly. See gh-653
Rudimentary implementation to support returning a
CompletableFuture
from a suspend fun.CoroutinesUtils.invokeSuspendingFunction
wraps the return value of the function in aMono
(orFlux
). But it also does this when aCompletableFuture
is returned, and thus results in aMono<CompletableFuture<?>>
, which isn't captured by graphql-java, and thus the dataloader is never dispatched.By unwrapping the future, and converting it to a mono (as opposed to wrapping), the dataloader seems to be dispatched correctly.
Haven't added any tests yet, but if you also think this would be the right approach I can work on adding some tests as well.
Related issue: #343