Regarding nested or sub-queries, is there some bes...
# graphql-kotlin
r
Regarding nested or sub-queries, is there some best practice? Given the following example: • there is a query for A • there is a query for B • B is a property of A and can be resolved if requested • This is how it looks from the interface level
Copy code
import 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:
Copy code
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.kt
d
You dont have to return data classes - if your object returns a function then it will only be invoked when requested, ie
Copy code
class 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)
r
Thanks for the hint. But that is more or less the same approach. With other graphql libraries (kickstart of nodeJS) you just register the DataLoaders and the Framework will resolve the graph of objects / nested resolvers
d
Not exactly
The way that GraphQL Java works (since it was based on the JS implementation I assume it is similar there as well) is that on incoming requests our queries are parsed into a tree which is then resolved in a breadth-first manner. When resolving a field we invoke underlying resolver (aka data fetcher). The difference between
graphql-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.
👍 1
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
👍 1