Skip to content

Commit 0a69755

Browse files
feat: honor LightDataFetcher on by level batching logic (#1795)
### 📝 Description Now that [PropertyDataFetcher](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/execution/PropertyDataFetcher.kt#L31) implements `LightDataFetcher` we need to make sure we respect that and on the `LEVEL_DISPATCHED` batching mechanism, decorate the DataFetcher properly depending if its `LightDataFetcher`, or just `DataFetcher`
1 parent 9344db7 commit 0a69755

File tree

4 files changed

+128
-29
lines changed

4 files changed

+128
-29
lines changed

executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ExecutionBatchState.kt

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.expediagroup.graphql.dataloader.instrumentation.level.state
1818

1919
import graphql.schema.DataFetcher
20+
import graphql.schema.LightDataFetcher
2021

2122
enum class LevelState { NOT_DISPATCHED, DISPATCHED }
2223

@@ -50,7 +51,7 @@ class ExecutionBatchState(documentHeight: Int) {
5051
*Array(documentHeight) { number -> Level(number + 1) to 0 }
5152
)
5253

53-
private val manuallyCompletableDataFetchers: MutableMap<Level, MutableList<ManuallyCompletableDataFetcher>> =
54+
private val manuallyCompletableDataFetchers: MutableMap<Level, MutableList<ManualDataFetcher>> =
5455
mutableMapOf(
5556
*Array(documentHeight) { number -> Level(number + 1) to mutableListOf() }
5657
)
@@ -59,7 +60,7 @@ class ExecutionBatchState(documentHeight: Int) {
5960
* Check if the [ExecutionBatchState] contains a level
6061
*
6162
* @param level to check if his state is being calculated
62-
* @return whether or not state contains the level
63+
* @return whether state contains the level
6364
*/
6465
fun contains(level: Level): Boolean = levelsState.containsKey(level)
6566

@@ -117,16 +118,22 @@ class ExecutionBatchState(documentHeight: Int) {
117118
* @param dataFetcher to be instrumented
118119
* @return instrumented dataFetcher
119120
*/
120-
fun toManuallyCompletableDataFetcher(level: Level, dataFetcher: DataFetcher<*>): ManuallyCompletableDataFetcher =
121-
ManuallyCompletableDataFetcher(dataFetcher).also { manuallyCompletableDataFetchers[level]?.add(it) }
121+
fun toManuallyCompletableDataFetcher(level: Level, dataFetcher: DataFetcher<*>): ManualDataFetcher {
122+
val manualDataFetcher = when (dataFetcher) {
123+
is LightDataFetcher<*> -> ManuallyCompletableLightDataFetcher(dataFetcher)
124+
else -> ManuallyCompletableDataFetcher(dataFetcher)
125+
}
126+
manuallyCompletableDataFetchers[level]?.add(manualDataFetcher)
127+
return manualDataFetcher
128+
}
122129

123130
/**
124131
* Complete all the [manuallyCompletableDataFetchers]
125132
*
126133
* @param level which level should complete dataFetchers
127134
*/
128135
fun completeDataFetchers(level: Level) {
129-
manuallyCompletableDataFetchers[level]?.forEach(ManuallyCompletableDataFetcher::complete)
136+
manuallyCompletableDataFetchers[level]?.forEach(ManualDataFetcher::complete)
130137
}
131138

132139
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2023 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.dataloader.instrumentation.level.state
18+
19+
import graphql.schema.DataFetcher
20+
import java.util.concurrent.CompletableFuture
21+
22+
/**
23+
* DataFetcher Decorator that allows manual completion of dataFetchers
24+
*/
25+
abstract class ManualDataFetcher : DataFetcher<CompletableFuture<Any?>> {
26+
val manualFuture: CompletableFuture<Any?> = CompletableFuture()
27+
var originalFuture: CompletableFuture<Any?>? = null
28+
var originalExpressionException: Exception? = null
29+
30+
/**
31+
* Manually complete the [manualFuture] by handling the [originalFuture]
32+
*/
33+
fun complete() {
34+
when {
35+
originalExpressionException != null -> manualFuture.completeExceptionally(originalExpressionException)
36+
else -> originalFuture?.handle { result, exception ->
37+
when {
38+
exception != null -> manualFuture.completeExceptionally(exception)
39+
else -> manualFuture.complete(result)
40+
}
41+
}
42+
}
43+
}
44+
}

executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ManuallyCompletableDataFetcher.kt

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2022 Expedia, Inc
2+
* Copyright 2023 Expedia, Inc
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,16 +31,11 @@ import java.util.concurrent.CompletableFuture
3131
*/
3232
class ManuallyCompletableDataFetcher(
3333
private val originalDataFetcher: DataFetcher<*>
34-
) : DataFetcher<CompletableFuture<Any?>> {
35-
36-
private val manualFuture: CompletableFuture<Any?> = CompletableFuture()
37-
private var originalFuture: CompletableFuture<Any?>? = null
38-
private var originalExpressionException: Exception? = null
39-
34+
) : ManualDataFetcher() {
4035
/**
41-
* when attempting to get the value from dataFetcher, store the execute the [originalDataFetcher]
36+
* when attempting to get the value from dataFetcher, execute the [originalDataFetcher]
4237
* and store the resulting future [originalFuture] and a possible [originalExpressionException] if
43-
* an exception was thrown during the expression
38+
* a synchronous exception was thrown during the execution
4439
*
4540
* @param environment dataFetchingEnvironment with information about the field
4641
* @return an uncompleted manualFuture that can be completed at later time
@@ -54,19 +49,4 @@ class ManuallyCompletableDataFetcher(
5449
}
5550
return manualFuture
5651
}
57-
58-
/**
59-
* Manually complete the [manualFuture] by handling the [originalFuture]
60-
*/
61-
fun complete() {
62-
when {
63-
originalExpressionException != null -> manualFuture.completeExceptionally(originalExpressionException)
64-
else -> originalFuture?.handle { result, exception ->
65-
when {
66-
exception != null -> manualFuture.completeExceptionally(exception)
67-
else -> manualFuture.complete(result)
68-
}
69-
}
70-
}
71-
}
7252
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2023 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.dataloader.instrumentation.level.state
18+
19+
import graphql.execution.Async
20+
import graphql.schema.DataFetchingEnvironment
21+
import graphql.schema.GraphQLFieldDefinition
22+
import graphql.schema.LightDataFetcher
23+
import java.util.concurrent.CompletableFuture
24+
import java.util.function.Supplier
25+
26+
/**
27+
* LightDataFetcher Decorator that stores the original dataFetcher result (it's always a completable future)
28+
* it stores the [originalFuture] as property and returns an uncompleted [manualFuture]
29+
* then at later point manually call [complete] to complete the [manualFuture] with the [originalFuture] result
30+
* to let ExecutionStrategy handle all futures
31+
*
32+
* @param originalDataFetcher original dataFetcher to be decorated
33+
*/
34+
class ManuallyCompletableLightDataFetcher(
35+
private val originalDataFetcher: LightDataFetcher<*>
36+
) : ManualDataFetcher(), LightDataFetcher<CompletableFuture<Any?>> {
37+
38+
override fun get(environment: DataFetchingEnvironment): CompletableFuture<Any?> =
39+
get(environment.fieldDefinition, environment.getSource()) { environment }
40+
41+
/**
42+
* when attempting to get the value from LightDataFetcher, execute the [originalDataFetcher]
43+
* and store the resulting future [originalFuture] and a possible [originalExpressionException] if
44+
* a synchronous exception was thrown during the execution
45+
*
46+
* @param fieldDefinition the graphql field definition
47+
* @param sourceObject the source object to get a value from
48+
* @param environmentSupplier a supplier of the [DataFetchingEnvironment] that creates it lazily
49+
* @return an uncompleted manualFuture that can be completed at later time
50+
*/
51+
override fun get(
52+
fieldDefinition: GraphQLFieldDefinition,
53+
sourceObject: Any?,
54+
environmentSupplier: Supplier<DataFetchingEnvironment>
55+
): CompletableFuture<Any?> {
56+
try {
57+
val fetchedValueRaw = originalDataFetcher.get(
58+
fieldDefinition,
59+
sourceObject,
60+
environmentSupplier
61+
)
62+
originalFuture = Async.toCompletableFuture(fetchedValueRaw)
63+
} catch (e: Exception) {
64+
originalExpressionException = e
65+
}
66+
return manualFuture
67+
}
68+
}

0 commit comments

Comments
 (0)