Skip to content

Commit 945c76f

Browse files
authored
[client] change GQL client to interface and add webclient implementation (ExpediaGroup#837)
Changed `graphql-kotlin-client` to a generic interface to allow custom implementations. Existing `GraphQLClient` is now a `GraphQLKtorClient`, a reference implementation for Ktor HTTP client based implementation of generic `GraphQLClient` interface. This change also introduced new Spring WebClient based reference implementation. When generating client code we default to target generic interface. In order to generate client code that allows per request customizations of Ktor/Spring WebClient plugins have to be explicitly configured to generate type specific client code. Updated/new modules: * clients/graphql-kotlin-client - new generic interface * clients/graphql-kotlin-ktor-client - Ktor HTTP Client based reference implementation of `GraphQLClient` * clients/graphql-kotlin-spring-client - Spring WebClient based reference implementation of `GraphQLClient`
1 parent 304954c commit 945c76f

File tree

47 files changed

+1275
-270
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1275
-270
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ Visit our [documentation site](https://expediagroup.github.io/graphql-kotlin) fo
1010

1111
## 📦 Modules
1212

13+
* [clients](/clients) - Lightweight GraphQL Kotlin HTTP clients based on Ktor HTTP client and Spring WebClient
1314
* [examples](/examples) - Example apps that use graphql-kotlin libraries to test and demonstrate usages
14-
* [graphql-kotlin-client](/graphql-kotlin-client) - Lightweight GraphQL Kotlin HTTP client
1515
* [graphql-kotlin-federation](/graphql-kotlin-federation) - Schema generator extension to build Apollo Federation GraphQL schemas
1616
* [graphql-kotlin-schema-generator](/graphql-kotlin-schema-generator) - Code only GraphQL schema generation for Kotlin
1717
* [graphql-kotlin-spring-server](/graphql-kotlin-spring-server) - Spring Boot auto-configuration library to create a GraphQL server
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# GraphQL Kotlin Client
2+
[![Maven Central](https://img.shields.io/maven-central/v/com.expediagroup/graphql-kotlin-client.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22com.expediagroup%22%20AND%20a:%22graphql-kotlin-client%22)
3+
[![Javadocs](https://img.shields.io/maven-central/v/com.expediagroup/graphql-kotlin-client.svg?label=javadoc&colorB=brightgreen)](https://www.javadoc.io/doc/com.expediagroup/graphql-kotlin-client)
4+
5+
This module defines an interface for a lightweight, typesafe GraphQL HTTP clients. See [graphql-kotlin-ktor-client](../graphql-kotlin-ktor-client)
6+
and [graphql-kotlin-spring-client](../graphql-kotlin-spring-client) for reference implementations.
7+
8+
NOTE: GraphQL clients can be invoked directly by manually creating your corresponding data model but since at its core it
9+
ends up with simple POST request this mode of operation is not that useful. Instead, GraphQL Kotlin clients should be used
10+
together with one of the GraphQL Kotlin build plugins to auto-generate type safe data models based on the specified queries.
11+
12+
## Features
13+
14+
* Supports query and mutation operations
15+
* Automatic generation of type-safe Kotlin models
16+
* Custom scalar support - defaults to String but can be configured to deserialize to specific types
17+
* Supports default enum values to gracefully handle new/unknown server values
18+
* Native support for coroutines
19+
* Easily configurable Ktor and Spring WebClient based HTTP Clients
20+
* Documentation generated from the underlying GraphQL schema
21+
22+
## Documentation
23+
24+
Additional information can be found in our [documentation](https://expediagroup.github.io/graphql-kotlin/docs/client/client-overview)
25+
and the [Javadocs](https://www.javadoc.io/doc/com.expediagroup/graphql-kotlin-client) of all published versions.
26+
27+
If you have a question about something you can not find in our documentation or Javadocs, feel free to
28+
[create an issue](https://github.com/ExpediaGroup/graphql-kotlin/issues) and tag it with the question label.
Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
description = "A lightweight typesafe GraphQL HTTP Client"
22

3-
val ktorVersion: String by project
43
val kotlinCoroutinesVersion: String by project
54

65
dependencies {
76
api(project(path = ":graphql-kotlin-types"))
87
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion")
9-
api("io.ktor:ktor-client-cio:$ktorVersion")
10-
api("io.ktor:ktor-client-json:$ktorVersion")
11-
api("io.ktor:ktor-client-jackson:$ktorVersion")
128
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2020 Expedia, Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.expediagroup.graphql.client
18+
19+
import com.expediagroup.graphql.types.GraphQLResponse
20+
21+
/**
22+
* A lightweight typesafe GraphQL HTTP client.
23+
*/
24+
interface GraphQLClient {
25+
26+
/**
27+
* Executes specified GraphQL query or mutation.
28+
*
29+
* NOTE: explicit result type Class parameter is required due to the type erasure at runtime, i.e. since generic type is erased at runtime our
30+
* default serialization would attempt to serialize results back to Any object. As a workaround we get raw results as String which we then
31+
* manually deserialize using passed in result type Class information.
32+
*/
33+
suspend fun <T> execute(query: String, operationName: String?, variables: Any?, resultType: Class<T>): GraphQLResponse<T>
34+
}
35+
36+
suspend inline fun <reified T> GraphQLClient.execute(query: String, operationName: String? = null, variables: Any? = null): GraphQLResponse<T> =
37+
this.execute(query, operationName, variables, T::class.java)

graphql-kotlin-client/README.md renamed to clients/graphql-kotlin-ktor-client/README.md

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,36 @@
1-
# GraphQL Kotlin Client
2-
[![Maven Central](https://img.shields.io/maven-central/v/com.expediagroup/graphql-kotlin-client.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22com.expediagroup%22%20AND%20a:%22graphql-kotlin-client%22)
3-
[![Javadocs](https://img.shields.io/maven-central/v/com.expediagroup/graphql-kotlin-client.svg?label=javadoc&colorB=brightgreen)](https://www.javadoc.io/doc/com.expediagroup/graphql-kotlin-client)
1+
# GraphQL Kotlin Ktor HTTP Client
2+
[![Maven Central](https://img.shields.io/maven-central/v/com.expediagroup/graphql-kotlin-ktor-client.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22com.expediagroup%22%20AND%20a:%22graphql-kotlin-ktor-client%22)
3+
[![Javadocs](https://img.shields.io/maven-central/v/com.expediagroup/graphql-kotlin-ktor-client.svg?label=javadoc&colorB=brightgreen)](https://www.javadoc.io/doc/com.expediagroup/graphql-kotlin-ktor-client)
44

5-
A lightweight, typesafe GraphQL HTTP client.
5+
`graphql-kotlin-ktor-client` provides Ktor HTTP client based reference implementation of `GraphQLClient`. `GraphQLKtorClient`
6+
is a thin wrapper on top of [Ktor HTTP Client](https://ktor.io/clients/index.html) and supports fully asynchronous non-blocking
7+
communication. It is highly customizable and can be configured with any supported Ktor HTTP [engine](https://ktor.io/clients/http-client/engines.html)
8+
and [features](https://ktor.io/clients/http-client/features.html).
69

7-
NOTE: GraphQL client can be invoked directly by manually creating your corresponding data model but since at its core it
8-
ends up with simple POST request this mode of operation is not that useful. Instead, GraphQL Kotlin client should be used
9-
together with one of the GraphQL Kotlin build plugins to auto-generate type safe data models based on the specified queries.
10+
`GraphQLKtorClient` uses the Ktor HTTP Client to execute the underlying queries. Clients can be customized with different
11+
engines (defaults to Coroutine-based IO) and HTTP client features. Custom configurations can be applied through Ktor DSL
12+
style builders.
1013

11-
## Features
12-
13-
* Supports query and mutation operations
14-
* Automatic generation of type-safe Kotlin models
15-
* Custom scalar support - defaults to String but can be configured to deserialize to specific types
16-
* Supports default enum values to gracefully handle new/unknown server values
17-
* Native support for coroutines
18-
* Easily configurable Ktor HTTP Client
19-
* Documentation generated from the underlying GraphQL schema
14+
See [Ktor HTTP Client documentation](https://ktor.io/clients/index.html) for additional details.
2015

2116
## Install it
2217

23-
Using a JVM dependency manager, link `graphql-kotlin-client` to your project.
18+
Using a JVM dependency manager, link `graphql-kotlin-ktor-client` to your project.
2419

2520
With Maven:
2621

2722
```xml
2823
<dependency>
2924
<groupId>com.expediagroup</groupId>
30-
<artifactId>graphql-kotlin-client</artifactId>
25+
<artifactId>graphql-kotlin-ktor-client</artifactId>
3126
<version>${latestVersion}</version>
3227
</dependency>
3328
```
3429

3530
With Gradle (example using kts):
3631

3732
```kotlin
38-
implementation("com.expediagroup:graphql-kotlin-client:$latestVersion")
33+
implementation("com.expediagroup:graphql-kotlin-ktor-client:$latestVersion")
3934
```
4035

4136
## Use it
@@ -78,10 +73,12 @@ details and considerations while writing your GraphQL queries.
7873

7974
GraphQL Kotlin build plugins will auto-generate your data classes based on your queries and the underlying GraphQL schema.
8075
In order to generate your client you will need to specify the target package name, schema file, and queries. If the queries
81-
parameter is omitted, it will default to using `*.graphql` files under your resources directory.
76+
parameter is omitted, it will default to using `*.graphql` files under your resources directory. In order to generate Ktor
77+
based client implementation you also need to specify client type.
8278

8379
```kotlin
8480
val graphqlGenerateClient by tasks.getting(GraphQLGenerateClientTask::class) {
81+
clientType.set(GraphQLClientType.KTOR)
8582
packageName.set("com.expediagroup.graphql.generated")
8683
// use schema file generated by the introspection task
8784
schemaFile.set(graphqlIntrospectSchema.outputFile)
@@ -95,13 +92,13 @@ Additional information about Gradle and Maven plugins as well as their respectiv
9592

9693
### Execute Queries
9794

98-
Your auto generated classes accept an instance of `GraphQLClient` which requires the target URL to be specified. `GraphQLClient`
95+
Your auto generated classes accept an instance of `GraphQLKtorClient` which requires the target URL to be specified. `GraphQLKtorClient`
9996
uses the Ktor HTTP client to execute the underlying queries and allows you to specify different engines (defaults to CIO) and
10097
HTTP client features. Please refer to [Ktor HTTP client documentation](https://ktor.io/clients/index.html) for additional
10198
details.
10299

103100
```kotlin
104-
val client = GraphQLClient(url = URL("http://localhost:8080/graphql"))
101+
val client = GraphQLKtorClient(url = URL("http://localhost:8080/graphql"))
105102
val query = MyAwesomeQuery(client)
106103
val result = query.execute()
107104
```
@@ -114,7 +111,7 @@ Additional information about Gradle and Maven plugins as well as their respectiv
114111
## Documentation
115112

116113
Additional information can be found in our [documentation](https://expediagroup.github.io/graphql-kotlin/docs/client/client-overview)
117-
and the [Javadocs](https://www.javadoc.io/doc/com.expediagroup/graphql-kotlin-client) of all published versions.
114+
and the [Javadocs](https://www.javadoc.io/doc/com.expediagroup/graphql-kotlin-ktor-client) of all published versions.
118115

119116
If you have a question about something you can not find in our documentation or Javadocs, feel free to
120117
[create an issue](https://github.com/ExpediaGroup/graphql-kotlin/issues) and tag it with the question label.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
description = "A lightweight typesafe GraphQL HTTP Client"
2+
3+
val ktorVersion: String by project
4+
val wireMockVersion: String by project
5+
6+
dependencies {
7+
api(project(path = ":graphql-kotlin-client"))
8+
api("io.ktor:ktor-client-cio:$ktorVersion")
9+
api("io.ktor:ktor-client-json:$ktorVersion")
10+
api("io.ktor:ktor-client-jackson:$ktorVersion")
11+
testImplementation("io.ktor:ktor-client-okhttp:$ktorVersion")
12+
testImplementation("io.ktor:ktor-client-logging-jvm:$ktorVersion")
13+
testImplementation("com.github.tomakehurst:wiremock-jre8:$wireMockVersion")
14+
}

graphql-kotlin-client/src/main/kotlin/com/expediagroup/graphql/client/GraphQLClient.kt renamed to clients/graphql-kotlin-ktor-client/src/main/kotlin/com/expediagroup/graphql/client/GraphQLKtorClient.kt

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,15 @@ import java.io.Closeable
3939
import java.net.URL
4040

4141
/**
42-
* A lightweight typesafe GraphQL HTTP client.
42+
* A lightweight typesafe GraphQL HTTP client using Ktor HTTP client engine.
4343
*/
4444
@KtorExperimentalAPI
45-
open class GraphQLClient<in T : HttpClientEngineConfig>(
45+
open class GraphQLKtorClient<in T : HttpClientEngineConfig>(
4646
private val url: URL,
4747
engineFactory: HttpClientEngineFactory<T>,
4848
private val mapper: ObjectMapper = jacksonObjectMapper(),
4949
configuration: HttpClientConfig<T>.() -> Unit = {}
50-
) : Closeable {
50+
) : GraphQLClient, Closeable {
5151

5252
private val typeCache = mutableMapOf<Class<*>, JavaType>()
5353

@@ -71,7 +71,7 @@ open class GraphQLClient<in T : HttpClientEngineConfig>(
7171
* default serialization would attempt to serialize results back to Any object. As a workaround we get raw results as String which we then
7272
* manually deserialize using passed in result type Class information.
7373
*/
74-
open suspend fun <T> execute(query: String, operationName: String? = null, variables: Any? = null, resultType: Class<T>, requestBuilder: HttpRequestBuilder.() -> Unit = {}): GraphQLResponse<T> {
74+
open suspend fun <T> execute(query: String, operationName: String? = null, variables: Any? = null, resultType: Class<T>, requestBuilder: HttpRequestBuilder.() -> Unit): GraphQLResponse<T> {
7575
// Variables are simple data classes which will be serialized as map.
7676
// By using map instead of typed object we can eliminate the need to explicitly convert variables to a map
7777
val graphQLRequest = mapOf(
@@ -91,25 +91,26 @@ open class GraphQLClient<in T : HttpClientEngineConfig>(
9191
return mapper.readValue(rawResult, parameterizedType(resultType))
9292
}
9393

94+
override suspend fun <T> execute(query: String, operationName: String?, variables: Any?, resultType: Class<T>): GraphQLResponse<T> =
95+
execute(query, operationName, variables, resultType, {})
96+
9497
/**
9598
* Executes specified GraphQL query or mutation operation.
9699
*/
97-
suspend inline fun <reified T> execute(query: String, operationName: String? = null, variables: Any? = null, noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}): GraphQLResponse<T> {
98-
return execute(query, operationName, variables, T::class.java, requestBuilder)
99-
}
100+
suspend inline fun <reified T> execute(query: String, operationName: String? = null, variables: Any? = null, noinline requestBuilder: HttpRequestBuilder.() -> Unit): GraphQLResponse<T> =
101+
execute(query, operationName, variables, T::class.java, requestBuilder)
100102

101-
private fun <T> parameterizedType(resultType: Class<T>): JavaType {
102-
return typeCache.computeIfAbsent(resultType) {
103+
private fun <T> parameterizedType(resultType: Class<T>): JavaType =
104+
typeCache.computeIfAbsent(resultType) {
103105
mapper.typeFactory.constructParametricType(GraphQLResponse::class.java, resultType)
104106
}
105-
}
106107

107108
override fun close() {
108109
client.close()
109110
}
110111

111112
companion object {
112113
operator fun invoke(url: URL, mapper: ObjectMapper = jacksonObjectMapper(), config: HttpClientConfig<CIOEngineConfig>.() -> Unit = {}) =
113-
GraphQLClient(url = url, engineFactory = CIO, mapper = mapper, configuration = config)
114+
GraphQLKtorClient(url = url, engineFactory = CIO, mapper = mapper, configuration = config)
114115
}
115116
}

0 commit comments

Comments
 (0)