如何使用带有 ktor 框架的 graphql-kotlin 进行字段级解析器

Posted

技术标签:

【中文标题】如何使用带有 ktor 框架的 graphql-kotlin 进行字段级解析器【英文标题】:how to do field level resolvers using graphql-kotlin with ktor framework 【发布时间】:2019-10-17 06:34:30 【问题描述】:

如何使用 gaphql-kotlin 进行字段级(非根)解析器?我的代码工作 (API1Response 中的 date_range 为空,因为当查询中存在 date_range 时,我不知道如何调用我的第二个解析器函数 getAPI2Response()) 当每个查询单独调用时,因为我有两个挂起函数关注查询名称,但我想用一个基于字段(非根)签入查询的查询调用两个挂起函数。例如,在下面显示的代码(服务)中,如果我的查询是带有字段“name”、“id”、“date_range”的“getAPI1Response”,那么 graphql 应该调用 getAPI1Response 和 getAPI2Response 并将其映射到数据模型“API1Response”所以用户可以使用单个查询而不是触发两个查询。

从 graphql-kotlin 样本中检查了几个样本,但都指的是我没有使用的 springboot。

//DATA MODEL

data class API1Response(
    val name: String?,
    val id: Int?,
    val date_range: API2Response?
)

data class API2Response(
    val max_date_range: ZonedDateTime?,
    val min_date_range: ZonedDateTime?
)
//SERVICE
class Query() 
    private val httpClient = HttpClient()

// when query name is "getAPI1Response", this function get triggers
    suspend fun getAPI1Response(): API1Response 
        // call API 1 and map response to API1Response model.
        return resp.content.toInputStream().use 
                    jackson.readValue(it, API1Response::class.java)
                
    

//// when query name is "getAPI2Response", this function get triggers
    suspend fun getAPI2Response(): API2Response 
        // call API 2 and map response to API2Response model.
        return resp.content.toInputStream().use 
                    jackson.readValue(it, API2Response::class.java)
                
    

// GRAPHQL HANDLER

package com.my.package.graphql

import com.expedia.graphql.SchemaGeneratorConfig
import com.expedia.graphql.TopLevelObject
import com.expedia.graphql.hooks.SchemaGeneratorHooks
import com.expedia.graphql.toSchema
import com.my.package.errors.ErrorType
import com.my.package.http.*
import com.my.package.json.Jackson
import graphql.ExecutionInput
import graphql.GraphQL
import graphql.language.StringValue
import graphql.schema.*
import graphql.schema.idl.SchemaPrinter
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.response.respondText
import io.ktor.routing.Route
import io.ktor.routing.get
import io.ktor.routing.post
import java.time.LocalDate
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import kotlin.reflect.KClass
import kotlin.reflect.KType

class GraphQLHandler(
    mutations: List<Any>,
    queries: List<Any>
) : Handler 
    private val schema = toSchema(
        config = SchemaGeneratorConfig(
            supportedPackages = listOf("com.my.package"),
            hooks = CustomSchemaGeneratorHooks(),
            topLevelQueryName = "Query",
            topLevelMutationName = "Mutation"
        ),
        mutations = mutations.map  TopLevelObject(it) ,
        queries = queries.map  TopLevelObject(it) 
    )

    private val graphQL = GraphQL.newGraphQL(schema).build()

    override val path = "/graphql"

    override val routes: Route.() -> Unit = 
        post 
            postQuery(call)
        
    

    private suspend fun postQuery(call: ApplicationCall) 
        val reqBody = call.receiveJson(Map::class)
        val query = reqBody["query"] as? String
        if (query == null) 
            call.respondError(
                HttpStatusCode.BadRequest,
                ErrorType.bad_request,
                "missing or invalid query field in json"
            )
            return
        

        @Suppress("UNCHECKED_CAST")
        val variables = reqBody["variables"] as? Map<String, Any> ?: emptyMap()

        handleQuery(call, query, variables)
    

// main function which calls my concern suspend funciton mapping query name to function name
    private suspend fun handleQuery(call: ApplicationCall, query: String, variables: Map<String, Any>?) 
        val executionInput = ExecutionInput(query, null, call.request.authContext, null, variables)
        val result = graphQL.execute(executionInput).toSpecification()
        val statusCode = if (result.containsKey("errors")) HttpStatusCode.InternalServerError else HttpStatusCode.OK
        call.respondJson(statusCode, result, Jackson.all)
    


class CustomSchemaGeneratorHooks : SchemaGeneratorHooks 
    override fun willGenerateGraphQLType(type: KType): GraphQLType? = when (type.classifier as? KClass<*>) 
        ZonedDateTime::class -> graphqlZonedDateTimeType
        LocalDate::class -> graphqlLocalDateType
        else -> null
    


val graphqlLocalDateType = GraphQLScalarType("LocalDate",
    " ISO date format without an offset, such as '2011-12-03' ",
    object : Coercing<LocalDate, String> 
        //override few functions here
    
)

val graphqlZonedDateTimeType = GraphQLScalarType("ZonedDateTime",
    " ISO date-time format with an offset, such as '2011-12-03T10:15:30+01:00' ",
    object : Coercing<ZonedDateTime, String> 
        //override few functions here
    
)

【问题讨论】:

【参考方案1】:

这已在相关的 github 问题中得到解答: https://github.com/ExpediaDotCom/graphql-kotlin/issues/230

使用 Spring 的示例: https://github.com/ExpediaDotCom/graphql-kotlin/blob/master/example/src/main/kotlin/com/expedia/graphql/sample/query/SubQueries.kt

由于 graphql-kotlin 反射性地生成解析器,你必须像上面的例子那样做,或者直接使用 KGraphQL 或 graphql-java

另一个从查询链上游获取数据的示例是使用 DataFetcherEnvironment。在此处添加:#173

【讨论】:

【参考方案2】:

此问题已发布在我们的问题中:https://github.com/ExpediaDotCom/graphql-kotlin/issues/230

我们在那里回答了问题,但从更高级别的字段访问数据的最佳方式是通过DataFetcherEnvironment。在此处添加:https://github.com/ExpediaDotCom/graphql-kotlin/pull/173

然后,您可以在 API1Response 中拥有一个函数,该函数将在从 GraphQL 架构调用时运行

data class API1Response(
    val name: String?,
    val id: Int?
) 
    // Functions are also exposed as fields.
    // environment will not show up in the schema but be available at runtime.
    fun date_range(environment: DataFetchingEnvironment): API2Response? 
        // Somehow get dates from other args or further up the query chain
        return API2Response()
    

【讨论】:

以上是关于如何使用带有 ktor 框架的 graphql-kotlin 进行字段级解析器的主要内容,如果未能解决你的问题,请参考以下文章

如何在 ktor 框架中限制路由访问?

使用 ktor 发送推送通知

带有自签名证书的 ktor 客户端 https 请求

如何在 Ktor 中列出配置的路由

Ktor 1.0 发布:Kotlin Web 框架;GoLand 2018.3 正式版发布!| 更新

带有 ktor 的静态目录 - 不在“mysite.com/static/...”下