如何使用带有 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 进行字段级解析器的主要内容,如果未能解决你的问题,请参考以下文章