How to generate Kotlin DSL Client by GraphQL schema
source link: https://blog.kotlin-academy.com/how-to-generate-kotlin-dsl-client-by-graphql-schema-707fd0c55284
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
How to generate Kotlin DSL Client by GraphQL schema
Introducing the Kobby Plugin — a code generator of Kotlin GraphQL Client
On the one hand, the GraphQL schema uniquely defines the data model and the available operations of the service that implements it. On the other hand, Kotlin provides amazing opportunities for creating your own domain-specific languages (DSL). This way, you can write a domain-specific language to interact with your GraphQL service according to the published GraphQL schema. But writing such a code manually is Sisyphean labor. This is where the Kobby Plugin comes in, which can parse your service’s GraphQL schema and generate a client DSL for interaction. Let’s try it out!
The source code for the examples in this article is available in Kobby Gradle Tutorial and Kobby Maven Tutorial.
What will we get in the end?
If you are too lazy to read the whole article…
Query
Mutation
Subscription
Kobby Plugin setup
In the beginning was the Word…
But we will start with a schema of our service. By default, the Kobby Plugin looks for a GraphQL schema in files with graphqls
extensions in the project resources. For the sake of simplicity, we will place our schema in a single file named cinema.graphqls
:
This simple schema will allow us to try out all kinds of GraphQL operations — queries, mutations, and subscriptions.
Next, we have to configure the plugin. For Gradle projects it looks like this:
For Maven projects, this does not look so elegant:
There are explicit and implicit ways to configure the plugin. We used the implicit way by adding dependencies to the Jackson and Ktor libraries. The Kobby Plugin not only scans resources for schema files but also analyzes project dependencies. If Jackson is present in the dependencies, then the plugin adds Jackson annotations to the generated DTO classes to ensure JSON serialization. If Ktor is present in the project dependencies, then the plugin generates a default DSL adapter. We’ll talk about adapters in the next section.
Instantiate DSL Context
We have configured our plugin. Now we have to generate a DSL according to our schema. Run gradle build
for Gradle or mvn compile
for Maven and the plugin will find the cinema.graphqls
file and generate a DSL based on it:
The plugin will create the cinema.kt
file with the cinemaContextOf
builder function that instantiates the CinemaContext
interface — the entry point of the generated DSL:
We have to pass an instance of the CinemaAdapter
interface to the builder function to create the context. What is an adapter? The DSL context, generated by the Kobby Plugin, knows nothing about the transport layer and GraphQL communication protocol. The context implementation just builds the query and passes it to the adapter. And the adapter has to do all the dirty work — send the query to the server, and receive and deserialize the response. You can write your own adapter implementation or use the default adapter generated by the plugin.
We will use the default adapter. It uses Ktor to execute GraphQL queries and mutations over HTTP and to establish subscription sessions over WebSocket:
Executing Queries
We are ready to execute our first query. Let’s try to find a film with actors by ID. In GraphQL, this query looks like this:
In Kotlin, it looks like this:
The context.query
is suspending function, and does not block the current thread. What do we get as a result of executing the query? In GraphQL, the result is JSON that looks like this:
To navigate through the result, the plugin generates “entity” interfaces that look like this:
The context.query
function returns an instance of the Query
entity, so the navigation through the result looks like this:
Executing Mutations
Let’s create a new movie. In GraphQL, the mutation looks like this:
And we’ll get the result as a JSON that looks like this:
In Kotlin, this mutation looks like this:
The context.mutation
function returns an instance of the Mutation
entity interface and also is suspending function. So, our mutation does not block the current thread.
Establishing Subscriptions
Let’s subscribe for new films notifications. In GraphQL, the subscription looks like this:
With this subscription, we will receive JSON messages that look like this:
The semantics of the subscription operation in Kotlin is different from the semantics of the query and mutation operations. While the query and mutation just send a request and receive a response, the subscription creates a long-lived session to listen to incoming messages. So, we have to create an asynchronous listener to receive incoming messages:
Don’t worry, we are not blocking the thread in an infinite loop, because the subscribe
function and the receive
function are suspending functions.
The lifetime of the subscription session is the same as the execution time of the subscribe
function. When we enter the subscribe
function, a session is created, and when we exit it, the session is destroyed.
The receive
function returns an instance of the Subscription
entity interface for each incoming message.
What have I not covered in this article?
- I haven’t covered how to map scalars defined in a schema to Kotlin datatypes.
- I haven’t talked about how the plugin works with abstract data types.
- I haven’t covered how to customize the generated DSL using GraphQL directives.
- I haven’t talked about how the plugin supports server-side development.
And most importantly, I didn’t talk about how to use Kotlin extension functions to turn the generated DSL into a rich domain model on steroids. Perhaps I will cover this in future articles.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK