Skip to content

Overview of generated GraphQL DSL

dermakov edited this page Aug 7, 2021 · 17 revisions

Let's take a look at a DSL generated from a simple GraphQL schema (file example.graphqls):

type Query {
    filmCount: Int!
}

Context

By our schema file example.graphqls Kobby will generate Kotlin file example.kt (more about entry point configuration see here):

public fun exampleContextOf(adapter: ExampleAdapter): ExampleContext = ExampleContextImpl(adapter)

public interface ExampleContext {
    public suspend fun query(__projection: QueryProjection.() -> Unit): Query

    public suspend fun mutation(__projection: MutationProjection.() -> Unit): Mutation

    public fun subscription(__projection: SubscriptionProjection.() -> Unit):
            ExampleSubscriber<Subscription>
}

// ... skipped

The ExampleContext interface is an entry point to generated client DSL. It contains three functions - query , mutation and subscription - which provide the ability to perform the corresponding GraphQL operations. Our schema example.graphqls only defines a Query type, so the generated mutations and subscriptions are dummy. But the query function allows us to create and execute real GraphQL queries according to our schema. Let's try to execute a simple query and get a response.

Projection

First, we have to build our query:

query {
    filmCount
}

The query function argument __projection is responsible for building the query. It has a Kotlin lambda type with QueryProjection receiver:

public suspend fun query(__projection: QueryProjection.() -> Unit): Query

The QueryProjection is an interface, defined in entity/Query.kt file:

@ExampleDSL
public interface QueryProjection {
    public fun filmCount(): Unit
}

This "projection" interface allows us to write a query with syntax very similar to GraphQL's native syntax:

fun main() = runBlocking {
    val context: ExampleContext = exampleContextOf(createMyAdapter())
    val response: Query = context.query {
        filmCount()
    }
}

fun createMyAdapter(): ExampleAdapter =
    TODO("Let's look at adapters later")

We have used the exampleContextOf function, defined in example.kt file, to instantiate the ExampleContext interface. And then we called the query function to build the GraphQL query and get the response to the query.

Entity

The response to our query is JSON, that looks like:

{
  "data": {
    "filmCount": 25
  }
}

To represent the response, Kobby generates an "entity" interface, that holds the response data. For our GraphQL Query type, defined in the schema, the corresponding "entity" interface is the Query interface defined in entity/Query.kt file (just before the QueryProjection interface):

public interface Query : ExampleContext {
    public val filmCount: Int
}

The Query interface has the filmCount property that contains the value of the filmCount attribute in our JSON response:

val response: Query = context.query {
    filmCount()
}
println("Film count: ${response.filmCount}")

Note, that the Query interface extends ExampleContext interface. So, every "entity" interface that Kobby generates is an entry point for new GraphQL queries, mutations, and subscriptions. This enables us to use Kotlin extension functions for smart customization of the generated DSL.