DataLoaders in Spring #1533
-
I used the Graphql Kotlin spring server in my project. My Graphql Kotlin model is like this. data class Post(
val id: UUID?,
@UpperCase
val title: String,
val content: String?,
val status: String? = null,
val createdAt: LocalDateTime?,
val author: Author? = null,
val authorId: UUID? = null,
val comments: List<Comment>? = emptyList()
)
//enum class PostStatus {
// DRAFT, PENDING_MODERATION, PUBLISHED;
//}
data class Author(
val id: UUID?,
val name: String,
val email: String,
val createdAt: LocalDateTime? = null,
val posts: List<Post>? = emptyList()
)
data class Comment(
val id: UUID?,
val content: String,
val createdAt: LocalDateTime? = null,
val postId: UUID? = null
)
data class CreatePostInput(
val title: String,
val content: String,
)
data class CommentInput(
val content: String,
val postId: UUID
) The @Component
class AuthorsDataLoader(val authorService: AuthorService) : KotlinDataLoader<UUID, Author> {
companion object {
const val name = "AuthorsDataLoader"
}
override val dataLoaderName = name
override fun getDataLoader(): DataLoader<UUID, Author> {
return DataLoaderFactory.newDataLoader { keys, environment ->
loaderScope.future {
authorService.getAuthorByIdIn(keys).toList()
}
}
}
}
@Component
class CommentsDataLoader(val postService: PostService) : KotlinDataLoader<UUID, List<Comment>> {
companion object {
private val log = LoggerFactory.getLogger(CommentsDataLoader::class.java)
const val name = "CommentsDataLoader"
}
override val dataLoaderName = AuthorsDataLoader.name
override fun getDataLoader(): DataLoader<UUID, List<Comment>> {
return DataLoaderFactory.newMappedDataLoader { keys, environment ->
loaderScope.future {
val comments = postService.getCommentsByPostIdIn(keys).toList()
val mappedComments: MutableMap<UUID, List<Comment>> = mutableMapOf()
keys.forEach { mappedComments[it] = comments.filter { c -> c.postId == it } }
log.info("mapped comments: {}", mappedComments)
mappedComments
}
}
}
} I tried to create dataFetchers for them. @Component
class CommentsDataFetcher : DataFetcher<CompletableFuture<List<Comment>>> {
override fun get(environment: DataFetchingEnvironment): CompletableFuture<List<Comment>> {
val postId = environment.getSource<Post>().id
return environment.getValueFromDataLoader(CommentsDataLoader.name, postId)
}
}
@Component
class AuthorDataFetcher : DataFetcher<CompletableFuture<Author>> {
override fun get(environment: DataFetchingEnvironment): CompletableFuture<Author> {
val authorId = environment.getSource<Post>().authorId
return environment.getValueFromDataLoader(AuthorsDataLoader.name, authorId)
}
} But this does not work, in the above codes, it specified the Source(parent type) in codes. I have explored Gradle Kotlin Example, it created another SpringDataFetcherFactory, is it a must? In the code first framework, such Graphql Spqr, Microprofile Graphql, it uses a |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 9 replies
-
After exploring the example(Spring server), I copied all files in this executions folder, and register a Is there a simple way to resolve |
Beta Was this translation helpful? Give feedback.
-
Hello @hantsy please try to use a single discussion for the same topic, you have opened this having said that, do you have an example repo with a consistent repro ? |
Beta Was this translation helpful? Give feedback.
-
You need very careful about the async thread model as graphql-java async model is that way you can get a TLTR; you are wrong here override fun getDataLoader(): DataLoader<UUID, Author> {
return DataLoaderFactory.newDataLoader { keys, environment ->
loaderScope.future {
authorService.getAuthorByIdIn(keys).toList()
}
}
} you need to use override fun getDataLoader(): DataLoader<UUID, Author> {
return DataLoaderFactory.newDataLoader { keys, environment ->
loaderScope.async {
authorService.getAuthorByIdIn(keys).toList()
}.asCompletableFuture()
}
} this is where suspend functions are incompatible with |
Beta Was this translation helpful? Give feedback.
-
I tried to add the following to my Query, fun comments(postId:UUID, environment: DataFetchingEnvironment): CompletableFuture<List<Comment>> {
// val postId = environment.getSource<Post>().id
// log.debug("Fetching comments of post: $postId")
return environment.getValueFromDataLoader(CommentsDataLoader.name, postId)
}
fun author(authorId:UUID, environment: DataFetchingEnvironment): CompletableFuture<Author> {
// val authorId = environment.getSource<Post>().authorId
// log.debug("Fetching author of post: $authorId")
return environment.getValueFromDataLoader(AuthorsDataLoader.name, authorId)
} In fact, I do not want these two items generated as queries, they are fields of data class Post(
val id: UUID?,
@UpperCase
val title: String,
val content: String?,
val status: String? = null,
val createdAt: LocalDateTime?,
val authorId: UUID? = null,
val author: Author? = null,
val comments: List<Comment>? = emptyList(),
) It does not work. And from the coding style, I do not know where these functions are connected to the parent object |
Beta Was this translation helpful? Give feedback.
You need very careful about the async thread model as graphql-java async model is
CompletableFuture
, if your source uses suspendable functions you cannot just interop the suspend function with a future, instead you need to run the suspend fn in a coroutine scope and useDeferred
-- please check https://github.com/samuelAndalon/graphql-kotlin-batching-demo/blob/batching-showcase/src/main/kotlin/com/example/demo/DataLoaderConfiguration.kt#L47that way you can get a
Deferred
Object and then you interop the Deferred with aCompletableFuture
TLTR; you are wrong here