Skip to content

Commit 76cd369

Browse files
committed
docs
1 parent 492fb70 commit 76cd369

File tree

2 files changed

+144
-18
lines changed

2 files changed

+144
-18
lines changed

docs/docs/inheritance.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
---
2+
sidebar_position: 6
3+
---
4+
5+
# Inheritance
6+
7+
When dealing with GraphQL schema containing [field arguments](https://graphql.com/learn/arguments/),
8+
generating Kotlin code can be a bit tricky. This is because the generated code cannot in itself be the implementation
9+
in a resolver.
10+
11+
Here's an example:
12+
13+
```graphql
14+
type Query {
15+
resolveMyType: MyType!
16+
}
17+
18+
type MyType {
19+
resolveMe(input: String!): String!
20+
}
21+
```
22+
23+
Generated Kotlin:
24+
25+
```kotlin
26+
package com.types.generated
27+
28+
open class Query {
29+
open fun resolveMyType(): MyType = throw NotImplementedError("Query.resolveMyType must be implemented.")
30+
}
31+
32+
open class MyType {
33+
open fun resolveMe(input: String): String = throw NotImplementedError("MyType.resolveMe must be implemented.")
34+
}
35+
```
36+
37+
Source code:
38+
39+
```kotlin
40+
import com.expediagroup.graphql.server.operations.Query
41+
import com.types.generated.MyType as MyTypeInterface
42+
import com.types.generated.Query as QueryInterface
43+
44+
class MyQuery : Query, QueryInterface() {
45+
override fun resolveMyType(): MyTypeInterface = MyType()
46+
}
47+
48+
class MyType : MyTypeInterface() {
49+
override fun resolveMe(input: String): String = "Hello world!"
50+
}
51+
```
52+
53+
As you can see, the generated code is not part of the implementation. Rather, it becomes an interface to inherit from in your implementation.
54+
This enforces a type contract between the schema and your resolver code.
55+
56+
Note that GraphQL Kotlin will use the implementation classes to generate the schema, not the generated interfaces.
57+
This means that all `@GraphQLDescription` and `@Deprecated` annotations have to be added to implementation classes
58+
in order to be propagated to the resulting schema.
59+
60+
## Top Level Types
61+
62+
When dealing with top-level types, i.e. `Query` and `Mutation`, you can inherit from the corresponding generated class
63+
to enforce the type contract. This is fine as long as all of your resolvers are contained in the same Query or Mutation class.
64+
65+
```graphql
66+
type Query {
67+
foo: String!
68+
bar: String!
69+
}
70+
```
71+
72+
```kotlin
73+
import com.expediagroup.graphql.server.operations.Query
74+
import com.types.generated.Query as QueryInterface
75+
76+
class MyQuery : Query, QueryInterface() {
77+
override fun foo(): String = "Hello"
78+
override fun bar(): String = "World"
79+
}
80+
```
81+
82+
However, you might want to separate the implementation into multiple classes like so:
83+
84+
```kotlin
85+
import com.expediagroup.graphql.server.operations.Query
86+
87+
class FooQuery : Query {
88+
override fun foo(): String = "Hello"
89+
}
90+
91+
class BarQuery : Query {
92+
override fun bar(): String = "World"
93+
}
94+
```
95+
96+
If you try to inherit from the generated `Query` class, you will get an error during schema generation.
97+
98+
```kotlin
99+
import com.expediagroup.graphql.server.operations.Query
100+
import com.types.generated.Query as QueryInterface
101+
102+
class FooQuery : Query, QueryInterface() {
103+
override fun foo(): String = "Hello"
104+
}
105+
106+
class BarQuery : Query, QueryInterface() {
107+
override fun bar(): String = "World"
108+
}
109+
```
110+
111+
This is because the generated `Query` class contains both `foo` and `bar` fields, which creates a conflict when inherited by multiple implementation classes.
112+
113+
Instead, you should inherit from the field-level generated `Query` classes like so:
114+
115+
```kotlin
116+
import com.expediagroup.graphql.server.operations.Query
117+
import com.types.generated.FooQuery as FooQueryInterface
118+
import com.types.generated.BarQuery as BarQueryInterface
119+
120+
class FooQuery : Query, FooQueryInterface() {
121+
override fun foo(): String = "Hello"
122+
}
123+
124+
class BarQuery : Query, BarQueryInterface() {
125+
override fun bar(): String = "World"
126+
}
127+
```
128+
129+
This way, schema generation can complete without conflict, and you can separate your implementation into multiple classes!

docs/docs/recommended-usage.md

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ type Query {
2121
}
2222

2323
type MyType {
24-
field1: String!
25-
field2: String
24+
foo: String!
25+
bar: String
2626
}
2727
```
2828

@@ -38,8 +38,8 @@ open class Query {
3838
}
3939

4040
data class MyType(
41-
val field1: String,
42-
val field2: String? = null
41+
val foo: String,
42+
val bar: String? = null
4343
)
4444
```
4545

@@ -53,16 +53,15 @@ import com.types.generated.Query as QueryInterface
5353
class MyQuery : Query, QueryInterface() {
5454
override fun resolveMyType(input: String): MyType =
5555
MyType(
56-
field1 = myExpensiveCall1(),
57-
field2 = myExpensiveCall2()
56+
foo = myExpensiveCall1(),
57+
bar = myExpensiveCall2()
5858
)
5959
}
60-
6160
```
6261

6362
The resulting source code is extremely unperformant. The `MyType` class is a data class, which means
64-
that the `field1` and `field2` properties are both initialized when the `MyType` object is created, and
65-
`myExpensiveCall1()` and `myExpensiveCall2()` will both be called in sequence! Even if I only query for `field1`, not
63+
that the `foo` and `bar` properties are both initialized when the `MyType` object is created, and
64+
`myExpensiveCall1()` and `myExpensiveCall2()` will both be called in sequence! Even if I only query for `foo`, not
6665
only will `myExpensiveCall2()` still run, but it will also wait until `myExpensiveCall1()` is totally finished.
6766

6867
### Instead, use the `resolverInterfaces` config!
@@ -91,30 +90,28 @@ open class Query {
9190
}
9291

9392
open class MyType {
94-
open fun field1(): String = throw NotImplementedError("MyType.field1 must be implemented.")
95-
open fun field2(): String? = throw NotImplementedError("MyType.field2 must be implemented.")
93+
open fun foo(): String = throw NotImplementedError("MyType.foo must be implemented.")
94+
open fun bar(): String? = throw NotImplementedError("MyType.bar must be implemented.")
9695
}
9796
```
9897

9998
Source code:
10099

101100
```kotlin
102101
import com.types.generated.MyType as MyTypeInterface
103-
import com.expediagroup.graphql.generator.annotations.GraphQLIgnore
104102

105103
class MyQuery : Query, QueryInterface() {
106104
override fun resolveMyType(input: String): MyType = MyType()
107105
}
108106

109-
@GraphQLIgnore
110107
class MyType : MyTypeInterface() {
111-
override fun field1(): String = myExpensiveCall1()
112-
override fun field2(): String? = myExpensiveCall2()
108+
override fun foo(): String = myExpensiveCall1()
109+
override fun bar(): String? = myExpensiveCall2()
113110
}
114111
```
115112

116-
This code is much more performant! The `MyType` class is no longer a data class, so the `field1` and `field2` properties
117-
can now be resolved independently of each other. If I query for only `field1`, only `myExpensiveCall1()` will be called, and
118-
if I query for only `field2`, only `myExpensiveCall2()` will be called.
113+
This code is much more performant! The `MyType` class is no longer a data class, so the `foo` and `bar` properties
114+
can now be resolved independently of each other. If I query for only `foo`, only `myExpensiveCall1()` will be called, and
115+
if I query for only `bar`, only `myExpensiveCall2()` will be called.
119116

120117
Check out the [related GraphQL Kotlin docs](https://opensource.expediagroup.com/graphql-kotlin/docs/schema-generator/execution/fetching-data/) for more information on this topic.

0 commit comments

Comments
 (0)