Skip to content

Commit 631c74d

Browse files
dariuszkucsmyrick
andauthored
[docs] GraphQL client documentation and configuration fixes (#714)
* [docs] GraphQL client documentation and configuration fixes * fix ktlint * update GraphQL client to be parameterized and accept config through lambda * Update docs/client/client-customization.md Co-authored-by: Shane Myrick <[email protected]> * Update docs/client/client-overview.md Co-authored-by: Shane Myrick <[email protected]> * Update docs/client/client-overview.md Co-authored-by: Shane Myrick <[email protected]> * Update docs/client/client-overview.md Co-authored-by: Shane Myrick <[email protected]> * Update docs/client/client-overview.md Co-authored-by: Shane Myrick <[email protected]> * Update docs/client/client-overview.md Co-authored-by: Shane Myrick <[email protected]> * Update docs/client/client-overview.md Co-authored-by: Shane Myrick <[email protected]> * update generated client result type to just Result * update docs to match code Co-authored-by: Shane Myrick <[email protected]>
1 parent 656b5e0 commit 631c74d

File tree

19 files changed

+495
-74
lines changed

19 files changed

+495
-74
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
---
2+
id: client-customization
3+
title: Client Customization
4+
---
5+
6+
## Ktor HTTP Client Customization
7+
8+
`GraphQLClient` uses the Ktor HTTP Client to execute the underlying queries. Clients can be customized with different
9+
engines (defaults to Coroutine-based IO) and HTTP client features. Custom configurations can be applied through Ktor DSL
10+
style builders.
11+
12+
```kotlin
13+
val client = GraphQLClient(
14+
url = URL("http://localhost:8080/graphql"),
15+
engineFactory = OkHttp
16+
) {
17+
engine {
18+
config {
19+
connectTimeout(10, TimeUnit.SECONDS)
20+
readTimeout(60, TimeUnit.SECONDS)
21+
writeTimeout(60, TimeUnit.SECONDS)
22+
}
23+
}
24+
install(Logging) {
25+
logger = Logger.DEFAULT
26+
level = LogLevel.HEADERS
27+
}
28+
}
29+
```
30+
31+
See [Ktor HTTP Client documentation](https://ktor.io/clients/index.html) for additional details.
32+
33+
## Jackson Customization
34+
35+
`GraphQLClient` relies on Jackson to handle polymorphic types and default enum values. Due to the necessary logic to
36+
handle the above, currently we don't support other JSON libraries.
37+
38+
```kotlin
39+
val customObjectMapper = jacksonObjectMapper()
40+
val client = GraphQLClient(url = URL("http://localhost:8080/graphql"), mapper = customObjectMapper)
41+
```
42+
43+
## Deprecated Field Usage
44+
45+
Build plugins will automatically fail generation of a client if any of the specified query files are referencing
46+
deprecated fields. This ensures that your clients have to explicitly opt-in into deprecated usage by specifying
47+
`allowDeprecatedFields` configuration option.
48+
49+
## Custom GraphQL Scalars
50+
51+
By default, custom GraphQL scalars are serialized and [type-aliased](https://kotlinlang.org/docs/reference/type-aliases.html)
52+
to a String. GraphQL Kotlin plugins also support custom serialization based on provided configuration.
53+
54+
In order to automatically convert between custom GraphQL `UUID` scalar type and `java.util.UUID`, we first need to create
55+
our custom `ScalarConverter`.
56+
57+
```kotlin
58+
package com.example.client
59+
60+
import com.expediagroup.graphql.client.converter.ScalarConverter
61+
import java.util.UUID
62+
63+
class UUIDScalarConverter : ScalarConverter<UUID> {
64+
override fun toScalar(rawValue: String): UUID = UUID.fromString(rawValue)
65+
override fun toJson(value: UUID): String = value.toString()
66+
}
67+
```
68+
69+
And then configure build plugin by specifying
70+
* Custom GraphQL scalar name
71+
* Target class name
72+
* Converter that provides logic to map between GraphQL and Kotlin type
73+
74+
```kotlin
75+
graphql {
76+
packageName = "com.example.generated"
77+
endpoint = "http://localhost:8080/graphql"
78+
converters.put("UUID", ScalarConverterMapping("java.util.UUID", "com.example.UUIDScalarConverter"))
79+
}
80+
```
81+
82+
See [Gradle](https://expediagroup.github.io/graphql-kotlin/docs/plugins/gradle-plugin#generating-client-with-custom-scalars)
83+
and [Maven](https://expediagroup.github.io/graphql-kotlin/docs/plugins/maven-plugin#generating-client-with-custom-scalars)
84+
plugin documentation for additional details.

docs/client/client-features.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
---
2+
id: client-features
3+
title: Client Features
4+
---
5+
6+
## Polymorphic Types Support
7+
8+
GraphQL supports polymorphic types through unions and interfaces which can be represented in Kotlin as marker and
9+
regular interfaces. In order to ensure generated objects are not empty, GraphQL queries referencing polymorphic types
10+
have to **explicitly specify all implementations**. Polymorphic queries also have to explicitly request `__typename`
11+
field so it can be used to Jackson correctly distinguish between different implementations.
12+
13+
Given example schema
14+
15+
```graphql
16+
type Query {
17+
interfaceQuery: BasicInterface!
18+
}
19+
20+
interface BasicInterface {
21+
id: Int!
22+
name: String!
23+
}
24+
25+
type FirstInterfaceImplementation implements BasicInterface {
26+
id: Int!
27+
intValue: Int!
28+
name: String!
29+
}
30+
31+
type SecondInterfaceImplementation implements BasicInterface {
32+
floatValue: Float!
33+
id: Int!
34+
name: String!
35+
}
36+
```
37+
38+
We can query interface field as
39+
40+
```graphql
41+
query PolymorphicQuery {
42+
interfaceQuery {
43+
__typename
44+
id
45+
name
46+
... on FirstInterfaceImplementation {
47+
intValue
48+
}
49+
... on SecondInterfaceImplementation {
50+
floatValue
51+
}
52+
}
53+
}
54+
```
55+
56+
Which will generate following data model
57+
58+
```kotlin
59+
@JsonTypeInfo(
60+
use = JsonTypeInfo.Id.NAME,
61+
include = JsonTypeInfo.As.PROPERTY,
62+
property = "__typename"
63+
)
64+
@JsonSubTypes(value = [com.fasterxml.jackson.annotation.JsonSubTypes.Type(value =
65+
PolymorphicQuery.FirstInterfaceImplementation::class,
66+
name="FirstInterfaceImplementation"),com.fasterxml.jackson.annotation.JsonSubTypes.Type(value
67+
= PolymorphicQuery.SecondInterfaceImplementation::class, name="SecondInterfaceImplementation")])
68+
interface BasicInterface {
69+
val id: Int
70+
val name: String
71+
}
72+
73+
data class FirstInterfaceImplementation(
74+
override val id: Int,
75+
override val name: String,
76+
val intValue: Int
77+
) : PolymorphicQuery.BasicInterface
78+
79+
data class SecondInterfaceImplementation(
80+
override val id: Int,
81+
override val name: String,
82+
val floatValue: Float
83+
) : PolymorphicQuery.BasicInterface
84+
```
85+
86+
## Default Enum Values
87+
88+
Enums represent predefined set of values. Adding additional enum values could be a potentially breaking change as your
89+
clients may not be able to process it. GraphQL Kotlin Client automatically adds default `@JsonEnumDefaultValue __UNKNOWN_VALUE`
90+
to all generated enums as a catch all safeguard for handling new enum values.
91+
92+
## Auto Generated Documentation
93+
94+
GraphQL Kotlin build plugins automatically pull in GraphQL descriptions of the queried fields from the target schema and
95+
add it as KDoc to corresponding data models.
96+
97+
Given simple GraphQL object definition
98+
99+
```graphql
100+
"Some basic description"
101+
type BasicObject {
102+
"Unique identifier"
103+
id: Int!
104+
"Object name"
105+
name: String!
106+
}
107+
```
108+
109+
Will result in a corresponding auto generated data class
110+
111+
```kotlin
112+
/**
113+
* Some basic description
114+
*/
115+
data class BasicObject(
116+
/**
117+
* Unique identifier
118+
*/
119+
val id: Int,
120+
/**
121+
* Object name
122+
*/
123+
val name: String
124+
)
125+
```
126+
127+
## Native Support for Coroutines
128+
129+
GraphQL Kotlin Client is a thin wrapper on top of [Ktor HTTP Client](https://ktor.io/clients/index.html) which provides
130+
fully asynchronous communication through Kotlin coroutines. `GraphQLClient` exposes single `execute` method that will
131+
suspend your GraphQL operation until it gets a response back without blocking the underlying thread. In order to fetch
132+
data asynchronously and perform some additional computations at the same time you should wrap your client execution in
133+
`launch` or `async` coroutine builder and explicitly `await` for their results.
134+
135+
See [Kotlin coroutines documentation](https://kotlinlang.org/docs/reference/coroutines-overview.html) for additional details.

docs/client/client-overview.md

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
---
2+
id: client-overview
3+
title: Client Overview
4+
---
5+
6+
`graphql-kotlin-client` is a lightweight type-safe GraphQL HTTP client. Type-safe data models are generated at build time
7+
by the GraphQL Kotlin [Gradle](https://expediagroup.github.io/graphql-kotlin/docs/plugins/gradle-plugin) and
8+
[Maven](https://expediagroup.github.io/graphql-kotlin/docs/plugins/maven-plugin) plugins.
9+
10+
`GraphQLClient` is a thin wrapper on top of [Ktor HTTP Client](https://ktor.io/clients/index.html) and supports fully
11+
asynchronous non-blocking communication. It is highly customizable and can be configured with any supported Ktor HTTP
12+
[engine](https://ktor.io/clients/http-client/engines.html) and [features](https://ktor.io/clients/http-client/features.html).
13+
14+
## Project Configuration
15+
16+
GraphQL Kotlin provides both Gradle and Maven plugins to automatically generate your client code at build time. Once
17+
your data classes are generated, you can then execute their underlying GraphQL operations using `graphql-kotlin-client`
18+
runtime dependency.
19+
20+
Basic `build.gradle.kts` Gradle configuration:
21+
22+
```kotlin
23+
import com.expediagroup.graphql.plugin.gradle.graphql
24+
25+
plugins {
26+
id("com.expediagroup.graphql") version $latestGraphQLKotlinVersion
27+
}
28+
29+
dependencies {
30+
implementation("com.expediagroup:graphql-kotlin-client:$latestGraphQLKotlinVersion")
31+
}
32+
33+
graphql {
34+
endpoint = "http://localhost:8080/graphql"
35+
packageName = "com.example.generated"
36+
}
37+
```
38+
39+
Equivalent `pom.xml` Maven configuration
40+
41+
```xml
42+
<?xml version="1.0" encoding="UTF-8"?>
43+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
44+
<modelVersion>4.0.0</modelVersion>
45+
46+
<groupId>com.example</groupId>
47+
<artifactId>graphql-kotlin-maven-client-example</artifactId>
48+
<version>1.0.0-SNAPSHOT</version>
49+
50+
<properties>
51+
<graphql-kotlin.version>$latestGraphQLKotlinVersion</graphql-kotlin.version>
52+
</properties>
53+
54+
<dependencies>
55+
<dependency>
56+
<groupId>com.expediagroup</groupId>
57+
<artifactId>graphql-kotlin-client</artifactId>
58+
<version>${graphql-kotlin.version}</version>
59+
</dependency>
60+
</dependencies>
61+
62+
<build>
63+
<plugins>
64+
<plugin>
65+
<groupId>com.expediagroup</groupId>
66+
<artifactId>graphql-kotlin-maven-plugin</artifactId>
67+
<version>${graphql-kotlin.version}</version>
68+
<executions>
69+
<execution>
70+
<id>introspect-schema</id>
71+
<goals>
72+
<goal>introspectSchema</goal>
73+
</goals>
74+
<configuration>
75+
<endpoint>http://localhost:8080/graphql</endpoint>
76+
</configuration>
77+
</execution>
78+
<execution>
79+
<id>generate-client</id>
80+
<goals>
81+
<goal>generateClient</goal>
82+
</goals>
83+
<configuration>
84+
<packageName>com.example.generated</packageName>
85+
<schemaFile>${project.build.directory}/schema.graphql</schemaFile>
86+
</configuration>
87+
</execution>
88+
</executions>
89+
</plugin>
90+
</plugins>
91+
</build>
92+
</project>
93+
```
94+
95+
See [graphql-kotlin-client-example](https://github.com/dariuszkuc/graphql-kotlin-client-example) project for complete
96+
working examples of Gradle and Maven based projects.
97+
98+
## Generating GraphQL Client
99+
100+
By default, GraphQL Kotlin build plugins will attempt to generate GraphQL clients from all `*graphql` files located under
101+
`src/main/resources`. Queries are validated against the target GraphQL schema, which can be manually provided, retrieved by
102+
the plugins through introspection (as configured in examples above) or downloaded directly from a custom SDL endpoint.
103+
See our documentation for more details on supported [Gradle tasks](https://expediagroup.github.io/graphql-kotlin/docs/plugins/gradle-plugin#tasks)
104+
and [Maven Mojos](https://expediagroup.github.io/graphql-kotlin/docs/plugins/maven-plugin#goals).
105+
106+
When creating your GraphQL queries make sure to always specify an operation name and name the files accordingly. Each
107+
one of your query files will generate a corresponding Kotlin file with a class matching your operation
108+
name that will act as a wrapper for all corresponding data classes. For example, given `HelloWorldQuery.graphql` with
109+
`HelloWorldQuery` as the operation name, GraphQL Kotlin plugins will generate a corresponding `HelloWorldQuery.kt` file
110+
with a `HelloWorldQuery` class under the configured package.
111+
112+
For example, given a simple schema
113+
114+
```graphql
115+
type Query {
116+
helloWorld: String
117+
}
118+
```
119+
120+
And a corresponding `HelloWorldQuery.graphql` query
121+
122+
```graphql
123+
query HelloWorldQuery {
124+
helloWorld
125+
}
126+
```
127+
128+
Plugins will generate following client code
129+
130+
```kotlin
131+
package com.example.generated
132+
133+
import com.expediagroup.graphql.client.GraphQLClient
134+
import com.expediagroup.graphql.client.GraphQLResult
135+
import kotlin.String
136+
137+
const val HELLO_WORLD_QUERY: String = "query HelloWorldQuery {\n helloWorld\n}"
138+
139+
class HelloWorldQuery(
140+
private val graphQLClient: GraphQLClient
141+
) {
142+
suspend fun execute(): GraphQLResult<HelloWorldQuery.Result> =
143+
graphQLClient.execute(HELLO_WORLD_QUERY, "HelloWorldQuery", null)
144+
145+
data class Result(
146+
val helloWorld: String
147+
)
148+
}
149+
```
150+
151+
Generated classes requires an instance of `GraphQLClient` and exposes a single `execute` suspendable method that executes
152+
the underlying GraphQL operation using the provided client.
153+
154+
## Executing Queries
155+
156+
Your auto generated classes accept an instance of `GraphQLClient` which is a thin wrapper around Ktor HTTP client that
157+
ensures proper serialization and deserialization of your GraphQL objects. `GraphQLClient` requires target URL to be
158+
specified and defaults to fully asynchronous non-blocking [Coroutine-based IO engine](https://ktor.io/clients/http-client/engines.html#cio).
159+
160+
```kotlin
161+
package com.example.client
162+
163+
import com.expediagroup.graphql.client.GraphQLClient
164+
import com.expediagroup.graphql.generated.HelloWorldQuery
165+
import kotlinx.coroutines.runBlocking
166+
import java.net.URL
167+
168+
fun main() {
169+
val client = GraphQLClient(url = URL("http://localhost:8080/graphql"))
170+
val helloWorldQuery = HelloWorldQuery(client)
171+
runBlocking {
172+
val result = helloWorldQuery.execute()
173+
println("hello world query result: ${result.data?.helloWorld}")
174+
}
175+
client.close()
176+
}
177+
```

0 commit comments

Comments
 (0)