Try implementing GraphQL server using grahpql-java-tools (+ kotlin)

What is this article?

Using Kotlin + graphql-java-tools, we will summarize the process of implementing GraphQL server and returning a response.

Including in this article --Build a simple GraphQL server

Not included in this article --Countermeasures for N + 1 problem --Paging implementation

About graphql-java-tools

It provides the function as a GraphQL server. A tool called graphql-spring-boot also uses graphql-java-tools internally. The GraphQL server itself does not function as a web application server. You need to use any framework or library. In this article, we will use Ktor.

Implementation flow

  1. Define the schema for the graphql file
  2. Implement RootResolver
  3. Implement Resolver
  4. Implement Handler to handle GraphQL requests

About the difference between Root Resolver and Resolver

graphql-java-tools has two types of Resolver concepts. (Resolver is like Controller in MVC)

Consider implementing the following GraphQL schema. The data in which the parent Parent has the child Child.

example.graphql


type Query {
  parent: Parent!
}

type Parent {
  id: ID!
  name: String!
  child: Child!
}

type Child {
  id: ID!
  name: String!
}

If you want to return data with Has A relation like this with graphql-java-tools, you need to implement two types, RootResolver and Resolver. The roles of each are as follows.

RootResolver: Implement the processing when the Parent is referenced. Called with a request to the endpoint as a trigger.

Resolver: Implement the processing when Parent.Child is referenced. It is called when the registered parent class is called as a trigger.

It's hard to understand, so I'll explain it with an example.

When the type defined in ʻexmple.graphql` is expressed in Kotlin class, it becomes as follows.


data class Parent(
    val id: Int, 
    val name: String, 
    val childId: Int
)

data class Child(
    val id: Int,
    val name: String
)

Parent.child does not exist in the actual Class. To return Parent.child as a response, you need to use Parent.childId to associate Child. To do that, you need Resolver. It can receive the object Resolver when the specified class is returned as a response. Therefore, you can refer to childId by receiving Parent with ParentResolver, and you can get the associated Child.

Summary··· Processing when the endpoint defined in Query is referenced-> RootResolver What to do when a property of type returned by the endpoint is referenced-> Resolver

1. Define the schema

Let's see the flow of actually implementing the server.

First, let's define the GraphQL schema. This article does not cover GraphQL syntax, so please refer to another article for syntax.

Here, we will implement a simple Query process. Create a file called sample.graphql in any directory. The server created this time will read the definition of this file.

I defined the endpoint called samples to return Sample type data as a list.

graphql/sample.graphql

type Query {
  samples: [Sample!]!
}

type Sample {
  id: ID
  name: String
  user: User
}

type User {
  id: ID
  sampleId: ID
}

The corresponding data class is also defined in Kotlin.


data class Sample(
  val id: Int,
  val name: String,
  val userId: Int
)

data class User(
  val id: Int,
  val email: String
)

2. Implement RootResolver

We will implement RootResolver. Implements the processing when the endpoint samples are called. Originally I think that UseCase etc. will be called, but this time I will make it so that the same list is returned.

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)
    )
  }
}

There are two points.

  1. Inherit GraphQLQueryResolver. This time we will implement the operation of Query, so let's inheritGraphQLQueryResolver. Inherit GraphQLMutaionResolver when implementing the operation of Mutation. When implementing both processes, one RootResolver can inherit both GraphQLQueryResolver and GraphQLMutaionResolver.

  2. Prepare a method with the same name as Query defined in GraphQL. Since graphql / sample.graphql defines a query endpoint called samples, RootReposolver also needs to define a samples method. At this time, if the argument is specified on GraphQL side, it is necessary to receive the argument of the matching type by the method of RootReposolver.

3. Implement Resolver

We will implement SampleResolver. Implement the process when the property of Sample.user is called.

This SampleResolver is triggered when the Sample class is returned as a response. As usual, UseCase is not called and returns an object with a matching ʻid` from a fixed List.

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 }
  }
  
}

There are two points.

  1. Inherit GraphQLResolver. Let's inherit GraphQLResolver by specifying the object that Resolver wants to receive in the generics. (In this example, inherit GraphqlResolver <Sample>)

  2. Prepare a method with the same name as the property defined in graphql Since it implements the processing when Sample.user is referenced as defined in GraphQL, implement the ʻuser` method.

4. Implement Handler

Register the sample.graphql file and Resolver defined earlier in the Handler. This allows the GraphQL server to process your request.

GraphqlHander.kt

class GraphQLHander {

  /**
  *Build the GraphQL schema.
  *Register the schema and Resolver defined in filePath.
  **/
  fun init(): GraphQL {
    val filePath = "graphql/sample.graphql" 
    val schema = SchemaParser.newParser()
      //Read GraphQL definition
      .file(file)
      //Load Resolver and associate it with GraphQL endpoint
      .resolvers(listOf(RootSampleResolver(), SampleResolver())) 
      .build()
      .makeExecutableSchema()
  }

  /**
  *Handles GraphQL requests.
  **/
  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 processing can be performed by passing the GraphQL Request parameter to the ʻexecute` method of the defined Handler.

Let's implement using Ktor to perform GraphQL processing on the endpoint / graphql.

Routes.kt

//specification of path
@Location("/graphql")
//Setting parameters received in request
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(
        //Execute GraphQL processing
        handler.execute(query, operationName, variables, context).toSpecification()
      )
  }
}

Now when you start the server, the / graphql endpoint can accept GraphQL requests: smile:

At the end

This time I implemented a GraphQL server with a simple data structure. The actual code will be posted if there is demand.

I hope it will be of some help to you.

Recommended Posts

Try implementing GraphQL server using grahpql-java-tools (+ kotlin)
Try implementing GraphQL server in Java
Try using libGDX
Try using Maven
Try using powermock-mockito2-2.0.2
Try using GraalVM
Try using jmockit 1.48
Try using sql-migrate
Try using SwiftLint
Try using Log4j 2.0
Try implementing the Eratosthenes sieve using the Java standard library
Try using Axon Framework
Try using java.lang.Math methods
Try using PowerMock's WhiteBox
Try using Talend Part 2
Try using Talend Part 1
Try using F # list
Try using each_with_index method
Try using Spring JDBC
Implementing a large-scale GraphQL server in Java with Netflix DGS
Try launching a webAP server on the micro using Helidon