Mit Kotlin + graphql-java-tools werden wir den Prozess der Implementierung des GraphQL-Servers und der Rückgabe einer Antwort zusammenfassen.
Einschließlich in diesem Artikel
Nicht in diesem Artikel enthalten
Es bietet die Funktion als GraphQL-Server. Ein Tool namens graphql-spring-boot verwendet auch intern graphql-java-tools. Der GraphQL-Server selbst fungiert nicht als Webanwendungsserver. Sie müssen ein beliebiges Framework oder eine Bibliothek verwenden. In diesem Artikel werden wir Ktor verwenden.
graphql-java-tools hat zwei Arten von Resolver-Konzepten. (Resolver ist wie Controller in MVC)
Erwägen Sie die Implementierung des folgenden GraphQL-Schemas. Die Daten, in denen der Elternteil "Elternteil" das Kind "Kind" hat.
example.graphql
type Query {
parent: Parent!
}
type Parent {
id: ID!
name: String!
child: Child!
}
type Child {
id: ID!
name: String!
}
Wenn Sie Daten mit einer solchen Has A-Beziehung zu graphql-java-tools zurückgeben, müssen Sie zwei Typen implementieren: "RootResolver" und "Resolver". Die Rollen von jedem sind wie folgt.
RootResolver: Implementiert, was passiert, wenn auf das übergeordnete Element verwiesen wird. Wird mit einer Anfrage an den Endpunkt als Auslöser aufgerufen.
Resolver: Implementiert die Verarbeitung, wenn auf Parent.Child verwiesen wird. Es wird mit dem Zeitpunkt aufgerufen, zu dem die registrierte übergeordnete Klasse als Trigger aufgerufen wird.
Es ist schwer zu verstehen, deshalb erkläre ich es anhand eines Beispiels.
Angenommen, der in "exmple.graphql" definierte Typ wird wie folgt durch eine Kotlin-Klasse dargestellt.
data class Parent(
val id: Int,
val name: String,
val childId: Int
)
data class Child(
val id: Int,
val name: String
)
Parent.child
existiert nicht in der aktuellen Klasse.
Um "Parent.child" als Antwort zurückzugeben, müssen Sie "Parent.childId" verwenden, um "Child" zuzuordnen.
Dazu benötigen Sie "Resolver".
Es kann das Objekt "Resolver" empfangen, wenn die angegebene Klasse als Antwort zurückgegeben wird.
Daher können Sie auf childId
verweisen, indem Sie Parent
mit ParentResolver
empfangen, und Sie können das zugehörige Child
erhalten.
Zusammenfassung··· Verarbeitung, wenn auf den in Query definierten Endpunkt verwiesen wird-> RootResolver Was tun, wenn auf eine Eigenschaft eines vom Endpunkt zurückgegebenen Typs verwiesen wird-> Resolver?
Lassen Sie uns den Ablauf der tatsächlichen Implementierung des Servers sehen.
Definieren wir zunächst das GraphQL-Schema. Dieser Artikel behandelt nicht die Syntax von GraphQL. Informationen zur Syntax finden Sie in einem anderen Artikel.
Hier implementieren wir einen einfachen Abfrageprozess. Erstellen Sie eine Datei mit dem Namen sample.graphql in einem beliebigen Verzeichnis. Der diesmal erstellte Server liest die Definition dieser Datei.
Ich habe einen Endpunkt namens samples definiert, um Daten vom Sample-Typ als Liste zurückzugeben.
graphql/sample.graphql
type Query {
samples: [Sample!]!
}
type Sample {
id: ID
name: String
user: User
}
type User {
id: ID
sampleId: ID
}
Die entsprechende Datenklasse ist auch in Kotlin definiert.
data class Sample(
val id: Int,
val name: String,
val userId: Int
)
data class User(
val id: Int,
val email: String
)
Wir werden RootResolver implementieren. Implementiert, was passiert, wenn die Endpunkt-Samples aufgerufen werden. Ursprünglich denke ich, dass UseCase usw. aufgerufen wird, aber dieses Mal werde ich es so machen, dass dieselbe Liste zurückgegeben wird.
RootSampleResolver.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)
)
}
}
Es gibt zwei Punkte.
Erben Sie GraphQLQueryResolver
.
Dieses Mal werden wir die Operation "Abfrage" implementieren, also erben wir "GraphQLQueryResolver".
Erben Sie "GraphQLMutaionResolver", wenn Sie die Operation "Mutation" implementieren.
Wenn beide Prozesse implementiert sind, kann ein "RootResolver" sowohl "GraphQLQueryResolver" als auch "GraphQLMutaionResolver" erben.
Bereiten Sie eine Methode mit demselben Namen wie die in GraphQL definierte Abfrage vor.
Da graphql / sample.graphql
einen Abfrageendpunkt namens samples
definiert, muss RootReposolver auch eine samples
-Methode definieren.
Wenn das Argument auf der GraphQL-Seite angegeben ist, muss zu diesem Zeitpunkt ein Argument des übereinstimmenden Typs in der Methode von RootReposolver empfangen werden.
Wir werden SampleResolver implementieren. Implementiert die Verarbeitung, wenn die Eigenschaft von Sample.user aufgerufen wird.
Dieser SampleResolver wird ausgelöst, wenn die Sample-Klasse als Antwort zurückgegeben wird. Wie üblich wird UseCase nicht aufgerufen und gibt ein Objekt mit einer übereinstimmenden ID aus einer festen Liste zurück.
SampleResolver.kt
class SampleResolver: GraphQLResolver<Sample> {
private val users = listOf(
User(id = 1, email = "[email protected]"),
User(id = 2, email = "[email protected]"),
User(id = 3, email = "[email protected]")
)
fun user(input: Sample): User? {
return users.find { it.id == input.userId }
}
}
Es gibt zwei Punkte.
Erben Sie "GraphQLResolver".
Lassen Sie uns "GraphQLResolver" erben, indem wir das Objekt angeben, das Resolver in den Generika empfangen möchte. (In diesem Beispiel erben Sie "GraphqlResolver
Bereiten Sie eine Methode mit demselben Namen wie die in graphql definierte Eigenschaft vor Implementieren Sie die Methode "user", da sie die Verarbeitung implementiert, wenn auf "Sample.user" verwiesen wird, was in GraphQL definiert ist.
Registrieren Sie die zuvor im Handler definierte Datei sample.graphql und den Resolver. Dadurch kann der GraphQL-Server Ihre Anfrage verarbeiten.
GraphqlHander.kt
class GraphQLHander {
/**
*Erstellen Sie das GraphQL-Schema.
*Registrieren Sie das in filePath definierte Schema und den Resolver.
**/
fun init(): GraphQL {
val filePath = "graphql/sample.graphql"
val schema = SchemaParser.newParser()
//Lesen Sie die GraphQL-Definition
.file(file)
//Laden Sie den Resolver und ordnen Sie ihn dem GraphQL-Endpunkt zu
.resolvers(listOf(RootSampleResolver(), SampleResolver()))
.build()
.makeExecutableSchema()
}
/**
*Verarbeitet GraphQL-Anforderungen.
**/
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())
)
}
}
Die GraphQL-Verarbeitung kann durchgeführt werden, indem der GraphQL-Anforderungsparameter an die execute
-Methode des definierten Handlers übergeben wird.
Verwenden wir Ktor, um die GraphQL-Verarbeitung auf dem Endpunkt / graphql
zu implementieren.
Routes.kt
//Angabe des Pfades
@Location("/graphql")
//Festlegen der Parameter, die in der Anforderung empfangen werden sollen
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(
//Führen Sie die GraphQL-Verarbeitung aus
handler.execute(query, operationName, variables, context).toSpecification()
)
}
}
Wenn Sie jetzt den Server starten, kann der Endpunkt / graphql
GraphQL-Anforderungen akzeptieren: smile:
Diesmal habe ich einen GraphQL-Server mit einer einfachen Datenstruktur implementiert. Der aktuelle Code wird bei Bedarf veröffentlicht.
Ich hoffe, es wird Ihnen helfen.
Recommended Posts