Robert
03/20/2021, 7:40 AMimport com.expediagroup.graphql.annotations.GraphQLDescription
import com.expediagroup.graphql.spring.operations.Query
import org.springframework.stereotype.Component
@Component
interface QueryB : Query, SubQuery {
@GraphQLDescription("Get B")
suspend fun queryB(): B
// corresponds to the field name in parent model (e.g. A.b)
override val fieldName: String
get() = "b"
}
@Component
interface QueryA : Query {
@GraphQLDescription("Get A")
suspend fun queryA(): A
}
data class A(
val aValue: String,
val b: B? // corresponds with field name
)
data class B(
val bValue: String,
)
interface SubQuery {
/**
* Name when used in parent model
*/
val fieldName: String
}
To implement the resolver for A (including B) it's needed to:
• Inject resolver for B into A
• Execute some custom logic to see if the query for A also requested field B
This kina works, but is a bit cumbersome and works against the benefits of graphql magic resolving what is needed:
class QueryAImpl(private val queryB: QueryB) : QueryA {
override suspend fun queryA(): A {
val aValue = A("A")
val `was b requested as part of a?` = true
return if (`was b requested as part of a?`) {
aValue.copy(b = queryB.queryB())
} else aValue
}
}
I think the DataLoader approach could help here, or?
https://github.com/ExpediaGroup/graphql-kotlin/blob/master/examples/server/spring-[…]graphql/examples/server/spring/dataloaders/CompanyDataLoader.ktDariusz Kuc
03/20/2021, 1:52 PMclass A(val aValue): String {
suspend fun b(): B {}
}
If computation of B requires some beans - you could either add them as private/ignored values to the constructor of A OR autowire them directly to fun b(@Autowired private service: SomeService)
Robert
03/22/2021, 6:43 AMDariusz Kuc
03/22/2021, 2:06 PMgraphql-kotlin
and graphql-java
is that we abstract away the resolver piece and just invoke the underlying function - doesn't matter if it is a getter or a regular function.
graphql-java
gives you the property getters for free as well (uses reflections on the parent object to find getter based on the field name - since we build schema from a reflection we can register this when we build schema). As for the other resolvers, you have to write those yourself - you could put all the logic within a resolver and don't have any extra function calls.
Keep in mind though that if your resolver returns an object -> in Kotlin (and I guess in Java if you use final
) you have to initialize those objects with ALL properties. It doesn't matter whether all (or any) of those fields will be resolved, you still need to populate them to create an object. That is why in graphql-kotlin
if instead of a property you use a function that function will only be invoked IF requested by a query.DataLoader
won't magically solve your problem. It can potentially help with caching and optimizing some of the batching calls but you still need to instruct your resolver to invoke it. It doesn't matter whether you invoke the data loader directly from a resolver OR from invoked function, i.e. in your example above you still can use data loader from b()
that returns B