使用graqhql-java-tools(加上kotlin)来实现GraphQL服务器尝试
这篇文章是关于什么的?
使用Kotlin + graphql-java-tools来实现GraphQL服务器并返回响应的过程,我将整理如下。
使用本文的信息,在此文章中提到的内容是:
– 构建一个简单的GraphQL服务器。
这篇文章没有包含的内容:
– N+1问题的解决办法
– 翻页功能的实现
关于graphql-java-tools
此文章将使用Ktor作为任意的框架或库,将提供作为GraphQL服务器的功能。在内部,称为graphql-spring-boot的工具还使用graphql-java-tools。GraphQL服务器本身不具备作为Web应用程序服务器的功能。
实施的过程
-
- 对于graphql文件,我们需要定义一个schema。
-
- 我们需要实现Root Resolver。
-
- 我们需要实现Resolver。
- 我们需要实现一个处理GraphQL请求的Handler。
关于RootResolver和Resolver的区别
graphql-java-tools有两种不同的解析器概念。(解析器类似于MVC中的控制器)
我們考慮實現以下的 GraphQL Schema。
這是一個包含父項目 Parent 及其子項目 Child 的數據。
type Query {
parent: Parent!
}
type Parent {
id: ID!
name: String!
child: Child!
}
type Child {
id: ID!
name: String!
}
要返回具有Has A关系的数据,请使用graphql-java-tools,在这种情况下,需要实现RootResolver和Resolver这两种类型。它们各自的作用如下:
RootResolver:
实现了当父节点被引用时的处理逻辑。
当请求达到端点时,将触发并调用该处理逻辑。
解析器:
实现了当Parent.Child被引用时的处理。
注册的父类被调用的时机作为触发器,并被调用。
由于难以理解,我将通过举例来进行说明。
假设我们将example.graphql中定义的类型转换成Kotlin类后,结果如下所示。
data class Parent(
val id: Int,
val name: String,
val childId: Int
)
data class Child(
val id: Int,
val name: String
)
实际的Class中不存在Parent.child。
为了将Parent.child作为响应返回,需要使用Parent.childId来关联Child。
为此,需要使用Resolver。
当指定的类作为响应返回时,Resolver可以接收该对象。
因此,通过ParentResolver接收Parent以引用childId,可以获取相关联的Child。
概括起来就是:
当被查询定义的终点被引用时的处理方法 -> RootResolver
当终点返回的类型属性被引用时的处理方法 -> Resolver
1. 定义模式
我们来看看实际上实施服务器的步骤。
首先,我们要定义GraphQL的模式。
本文中没有解释GraphQL的语法,所以请参考另一篇文章了解语法。
请在这里实现一个简单的 Query 处理。
请在任意目录下创建一个名为 sample.graphql 的文件。
在本次创建的服务器中,将加载该文件的定义。
我尝试定义了一个名为”samples”的端点,该端点以列表形式返回”Sample”类型的数据。
以下是示例GraphQL文件的释义。
type Query {
samples: [Sample!]!
}
type Sample {
id: ID
name: String
user: User
}
type User {
id: ID
sampleId: ID
}
我們也會在Kotlin中定義對應的資料類。
data class Sample(
val id: Int,
val name: String,
val userId: Int
)
data class User(
val id: Int,
val email: String
)
2. 实现 RootResolver
(Note: This translation assumes that “RootResolver” refers to a specific term or concept in the context of programming or technology. If there is more context or specific meaning behind “RootResolver,” please provide more details for a more accurate translation.)
我们将实现 RootReslover。
我们将实现当调用 samples 端点时的处理。
通常情况下,我们会调用 UseCase 等,但是这次我们将制作一个返回相同列表的函数。
根样本解析程序.kt
class RootSampleResolver: GraphQLQueryResolver {
fun samples(): List<Sample> {
return listOf(
Sample(id = 1, name = "sample1", userId = 1),
Sample(id = 2, name = "sample2", userId = 2),
Sample(id = 3, name = "sample3", userId = 3)
)
}
}
以下是漢語的原生釋義:
重點有兩個:
1. 繼承GraphQLQueryResolver。
因為我們這次是實現Query的操作,所以應該繼承GraphQLQueryResolver。
如果想實現Mutation的操作,則應該繼承GraphQLMutationResolver。
如果需要實現兩者的操作,也可以在一個RootResolver中同時繼承GraphQLQueryResolver和GraphQLMutationResolver。
-
- 准备一个与在GraphQL中定义的查询相同名称的方法。
-
- 在graphql/sample.graphql中,定义了名为samples的查询节点,因此在RootReposolver中也需要定义samples方法。
- 此时,如果在GraphQL端指定了参数,那么RootReposolver的方法必须接收与之匹配的参数类型。
实现Resolver
我們將實現 SampleResolver。
我們將實現當調用 Sample.user 的屬性時的處理。
这个SampleResolver将在返回Sample类作为响应的时候触发。
与往常一样,UseCase不会被调用,而是从固定的List中返回与id匹配的对象。
SampleResolver.kt 的中文本地化处理
class SampleResolver: GraphQLResolver<Sample> {
private val users = listOf(
User(id = 1, email = "user1@hoge.com"),
User(id = 2, email = "user2@hoge.com"),
User(id = 3, email = "user3@hoge.com")
)
fun user(input: Sample): User? {
return users.find { it.id == input.userId }
}
}
重点有两个。
-
- 继承GraphQLResolver类。
-
- 通过为Resolver指定要接收的对象类型的泛型参数,在这个例子中继承GraphqlResolver。
-
- 准备一个与在graphql中定义的属性同名的方法。
- 由于要实现对在GraphQL中定义的Sample.user属性的引用处理,因此需要实现一个名为user的方法。
4. 实现Handler
我们将前面定义的 sample.graphql 文件和 Resolver 注册到 Handler 中。
这样一来,我们就能在 GraphQL 服务器上处理请求了。
GraphqlHander.kt的汉语本地化为:Graphql处理器.kt
class GraphQLHander {
/**
* GraphQLのスキーマをビルドします。
* filePathに定義されているスキーマとResolverを登録します。
**/
fun init(): GraphQL {
val filePath = "graphql/sample.graphql"
val schema = SchemaParser.newParser()
// GraphQLの定義を読み込む
.file(file)
// Resolverを読み込ませて、GraphQLのエンドポイントと紐づける
.resolvers(listOf(RootSampleResolver(), SampleResolver()))
.build()
.makeExecutableSchema()
}
/**
* GraphQLのリクエストを処理します。
**/
fun execute(query: String, operationName: String, variables: Map<String, Any>, context: Any): ExecutionResult {
val graphql = init()
return graphql.execute(
ExecutionInput.newExecutionInput()
.query(query)
.operationName(operationName)
.variables(variables)
.context(context)
.dataLoaderRegistry(dataLoaderProvider.provideDataLoaderRegistry())
)
}
}
可以通过将GraphQL请求参数传递到定义的Handler的execute方法中来执行GraphQL处理。
我将使用 Ktor,在 /graphql 这个端点上进行 GraphQL 的处理实现。
Routes.kt 的中文释义为「路径.kt」。
// pathの指定
@Location("/graphql")
// リクエストで受け取るパラメータの設定
data class GraphQLRequest(val query: String = "", val operationName: String = "", val variables: Map<String, Any> = mapOf())
fun Application.routes() {
val handler = GraphQLHandler()
post<GraphQLRequest> {
val request = call.receive<GraphQLRequest>()
val query = request.query
val operationName = request.operationName
val variables = request.variables
val context = ConcurrentHashMap<String, Any>()
call.respond(
// GraphQLの処理を実行
handler.execute(query, operationName, variables, context).toSpecification()
)
}
}
现在,当启动服务器后,可以通过/graphql端点接收到GraphQL请求。
结束
这次我实现了一个简单的GraphQL服务器,数据结构简单。
如果有需求的话,我可以展示实际的代码。
如果能对您有一点帮助,我将感到非常幸运。