Skip to content

Commit ca13568

Browse files
author
Samuel Vazquez
committed
feat: initial commit
1 parent eea0182 commit ca13568

File tree

9 files changed

+88
-63
lines changed

9 files changed

+88
-63
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2025 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.syncexhaustion
18+
19+
import com.expediagroup.graphql.dataloader.KotlinDataLoaderInstrumentation
20+
import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.state.SyncExecutionExhaustedState
21+
import org.dataloader.instrumentation.DataLoaderInstrumentationContext
22+
23+
internal class DataLoaderInstrumentationContext(
24+
private val syncExecutionExhaustedState: SyncExecutionExhaustedState
25+
): DataLoaderInstrumentationContext<Any?> {
26+
override fun onDispatched() {
27+
syncExecutionExhaustedState.onDataLoaderPromiseDispatched()
28+
}
29+
override fun onCompleted(result: Any?, t: Throwable?) {
30+
syncExecutionExhaustedState.onDataLoaderPromiseCompleted()
31+
}
32+
}
33+
34+
class DataLoaderInstrumentationForSyncExecutionExhausted(
35+
syncExecutionExhaustedState: SyncExecutionExhaustedState
36+
) : KotlinDataLoaderInstrumentation(
37+
DataLoaderInstrumentationContext(syncExecutionExhaustedState)
38+
)

executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/state/SyncExecutionExhaustedState.kt

+11-6
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,7 @@ import java.util.concurrent.atomic.AtomicInteger
3939
* Orchestrate the [ExecutionBatchState] of all [ExecutionInput] sharing the same [GraphQLContext],
4040
* when a certain state is reached will invoke [OnSyncExecutionExhaustedCallback]
4141
*/
42-
class SyncExecutionExhaustedState(
43-
totalOperations: Int,
44-
private val dataLoaderRegistry: KotlinDataLoaderRegistry
45-
) {
42+
class SyncExecutionExhaustedState(totalOperations: Int) {
4643
private val totalExecutions: AtomicInteger = AtomicInteger(totalOperations)
4744
val executions = ConcurrentHashMap<ExecutionId, ExecutionBatchState>()
4845

@@ -128,7 +125,7 @@ class SyncExecutionExhaustedState(
128125

129126
return object : FieldFetchingInstrumentationContext {
130127
override fun onFetchedValue(fetchedValue: Any?) {
131-
println("${fieldName}:$path")
128+
//println("${fieldName}:$path")
132129
executions.computeIfPresent(executionId) { _, executionState ->
133130
executionState.fieldToDispatchedState(field, fieldExecutionStrategyPath, fieldGraphQLType, fetchedValue)
134131
executionState
@@ -141,7 +138,7 @@ class SyncExecutionExhaustedState(
141138

142139
override fun onCompleted(result: Any?, t: Throwable?) {
143140
executions.computeIfPresent(executionId) { _, executionState ->
144-
println("${fieldName}:$path")
141+
println("completed: ${fieldName}:$path")
145142
executionState.fieldToCompletedState(field, fieldExecutionStrategyPath, result)
146143
executionState
147144
}
@@ -156,6 +153,14 @@ class SyncExecutionExhaustedState(
156153
}
157154
}
158155

156+
fun onDataLoaderPromiseDispatched() {
157+
dataLoaderRegistry.onDataLoaderPromiseDispatched()
158+
}
159+
160+
fun onDataLoaderPromiseCompleted() {
161+
dataLoaderRegistry.onDataLoaderPromiseCompleted()
162+
}
163+
159164
/**
160165
* execute a given [predicate] when all [ExecutionInput] sharing a [GraphQLContext] exhausted their execution.
161166
* A Synchronous Execution is considered Exhausted when all [DataFetcher]s of all paths were executed up until

executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/fixture/AstronautGraphQL.kt

-3
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import com.expediagroup.graphql.dataloader.instrumentation.fixture.datafetcher.P
3232
import com.expediagroup.graphql.dataloader.instrumentation.fixture.datafetcher.PlanetServiceRequest
3333
import com.expediagroup.graphql.dataloader.instrumentation.fixture.domain.Mission
3434
import com.expediagroup.graphql.dataloader.instrumentation.fixture.domain.Nasa
35-
import com.expediagroup.graphql.dataloader.DataLoaderDependantsStateInstrumentation
3635
import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.state.SyncExecutionExhaustedState
3736
import graphql.ExecutionInput
3837
import graphql.ExecutionResult
@@ -43,13 +42,11 @@ import graphql.schema.idl.RuntimeWiring
4342
import graphql.schema.idl.SchemaGenerator
4443
import graphql.schema.idl.SchemaParser
4544
import graphql.schema.idl.TypeRuntimeWiring
46-
import io.mockk.mockk
4745
import io.mockk.spyk
4846
import kotlinx.coroutines.async
4947
import kotlinx.coroutines.awaitAll
5048
import kotlinx.coroutines.future.await
5149
import kotlinx.coroutines.runBlocking
52-
import org.dataloader.instrumentation.ChainedDataLoaderInstrumentation
5350

5451
object AstronautGraphQL {
5552
private val schema = """

executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/fixture/ProductGraphQL.kt

-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import com.expediagroup.graphql.dataloader.instrumentation.fixture.datafetcher.P
2424
import com.expediagroup.graphql.dataloader.instrumentation.fixture.domain.Product
2525
import com.expediagroup.graphql.dataloader.instrumentation.fixture.domain.ProductDetails
2626
import com.expediagroup.graphql.dataloader.instrumentation.fixture.domain.ProductSummary
27-
import com.expediagroup.graphql.dataloader.DataLoaderDependantsStateInstrumentation
2827
import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.state.SyncExecutionExhaustedState
2928
import graphql.ExecutionInput
3029
import graphql.ExecutionResult
@@ -36,13 +35,11 @@ import graphql.schema.idl.RuntimeWiring
3635
import graphql.schema.idl.SchemaGenerator
3736
import graphql.schema.idl.SchemaParser
3837
import graphql.schema.idl.TypeRuntimeWiring
39-
import io.mockk.mockk
4038
import io.mockk.spyk
4139
import kotlinx.coroutines.async
4240
import kotlinx.coroutines.awaitAll
4341
import kotlinx.coroutines.future.await
4442
import kotlinx.coroutines.runBlocking
45-
import org.dataloader.instrumentation.ChainedDataLoaderInstrumentation
4643
import java.util.concurrent.CompletableFuture
4744

4845
object ProductGraphQL {
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import org.junit.jupiter.api.Test
2727
import kotlin.test.assertEquals
2828
import kotlin.test.assertTrue
2929

30-
class DataLoaderSyncExecutionExhaustedInstrumentationTest {
30+
class DataLoaderInstrumentationForSyncExecutionExhaustedTest {
3131
private val dataLoaderSyncExecutionExhaustedInstrumentation = DataLoaderSyncExecutionExhaustedInstrumentation()
3232
private val astronautGraphQL = AstronautGraphQL.builder
3333
.instrumentation(dataLoaderSyncExecutionExhaustedInstrumentation)
@@ -71,7 +71,7 @@ class DataLoaderSyncExecutionExhaustedInstrumentationTest {
7171
assertEquals(1, missionStatistics?.batchInvokeCount)
7272
assertEquals(2, missionStatistics?.batchLoadCount)
7373

74-
verify(exactly = 2) {
74+
verify(exactly = 1) {
7575
kotlinDataLoaderRegistry.dispatchAll()
7676
}
7777
}
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,16 @@
1616

1717
package com.expediagroup.graphql.dataloader
1818

19-
import graphql.GraphQLContext
2019
import org.dataloader.DataLoader
2120
import org.dataloader.instrumentation.DataLoaderInstrumentation
2221
import org.dataloader.instrumentation.DataLoaderInstrumentationContext
2322

24-
class DataLoaderDependantsStateInstrumentation(
25-
private val graphQLContext: GraphQLContext
23+
open class KotlinDataLoaderInstrumentation(
24+
private val context: DataLoaderInstrumentationContext<Any?>
2625
) : DataLoaderInstrumentation {
2726
override fun beginLoad(
2827
dataLoader: DataLoader<*, *>,
2928
key: Any,
3029
loadContext: Any?
31-
): DataLoaderInstrumentationContext<Any> =
32-
object : DataLoaderInstrumentationContext<Any> {
33-
override fun onDispatched() {
34-
graphQLContext
35-
.get<KotlinDataLoaderRegistry>(KotlinDataLoaderRegistry::class)
36-
?.onDataLoaderPromiseDispatched()
37-
}
38-
override fun onCompleted(result: Any?, t: Throwable?) {
39-
graphQLContext
40-
.get<KotlinDataLoaderRegistry>(KotlinDataLoaderRegistry::class)
41-
?.onDataLoaderPromiseCompleted()
42-
}
43-
}
30+
): DataLoaderInstrumentationContext<Any?> = context
4431
}

executions/graphql-kotlin-dataloader/src/main/kotlin/com/expediagroup/graphql/dataloader/KotlinDataLoaderRegistryFactory.kt

+9-15
Original file line numberDiff line numberDiff line change
@@ -25,31 +25,25 @@ import org.dataloader.instrumentation.DataLoaderInstrumentation
2525
* Generates a [KotlinDataLoaderRegistry] with the configuration provided by all [KotlinDataLoader]s.
2626
*/
2727
class KotlinDataLoaderRegistryFactory(
28-
private val dataLoaders: List<KotlinDataLoader<*, *>>,
29-
private val dataLoaderInstrumentations: List<DataLoaderInstrumentation>
28+
private val dataLoaders: List<KotlinDataLoader<*, *>>
3029
) {
31-
constructor(): this(emptyList(), emptyList())
32-
constructor(dataLoaders: List<KotlinDataLoader<*, *>>): this(dataLoaders, emptyList())
3330
/**
3431
* Generate [KotlinDataLoaderRegistry] to be used for GraphQL request execution.
3532
*/
36-
fun generate(graphQLContext: GraphQLContext): KotlinDataLoaderRegistry {
33+
fun generate(
34+
graphQLContext: GraphQLContext,
35+
dataLoaderInstrumentation: DataLoaderInstrumentation? = null,
36+
): KotlinDataLoaderRegistry {
3737
val builder = DataLoaderRegistry.newRegistry()
38-
builder.instrumentation(
39-
ChainedDataLoaderInstrumentation(
40-
dataLoaderInstrumentations.toMutableList().also {
41-
it.add(DataLoaderDependantsStateInstrumentation(graphQLContext))
42-
}
43-
)
44-
)
38+
dataLoaderInstrumentation?.let {
39+
builder.instrumentation(dataLoaderInstrumentation)
40+
}
4541
dataLoaders.forEach { kotlinDataLoader ->
4642
builder.register(
4743
kotlinDataLoader.dataLoaderName,
4844
kotlinDataLoader.getDataLoader(graphQLContext)
4945
)
5046
}
51-
return KotlinDataLoaderRegistry(builder.build()).also {
52-
graphQLContext.put(KotlinDataLoaderRegistry::class, it)
53-
}
47+
return KotlinDataLoaderRegistry(builder.build())
5448
}
5549
}

servers/graphql-kotlin-server/src/main/kotlin/com/expediagroup/graphql/server/execution/GraphQLRequestHandler.kt

+23-16
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package com.expediagroup.graphql.server.execution
1818

1919
import com.expediagroup.graphql.dataloader.KotlinDataLoaderRegistry
2020
import com.expediagroup.graphql.dataloader.KotlinDataLoaderRegistryFactory
21+
import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.DataLoaderInstrumentationForSyncExecutionExhausted
2122
import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.DataLoaderSyncExecutionExhaustedInstrumentation
2223
import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.state.SyncExecutionExhaustedState
2324
import com.expediagroup.graphql.generator.extensions.plus
@@ -46,6 +47,7 @@ import kotlinx.coroutines.flow.flowOf
4647
import kotlinx.coroutines.flow.map
4748
import kotlinx.coroutines.future.await
4849
import kotlinx.coroutines.supervisorScope
50+
import org.dataloader.instrumentation.DataLoaderInstrumentation
4951

5052
open class GraphQLRequestHandler(
5153
private val graphQL: GraphQL,
@@ -73,21 +75,25 @@ open class GraphQLRequestHandler(
7375
*/
7476
open suspend fun executeRequest(
7577
graphQLRequest: GraphQLServerRequest,
76-
graphQLContext: GraphQLContext = GraphQLContext.of(emptyMap<Any, Any>())
78+
graphQLContext: GraphQLContext = GraphQLContext.getDefault()
7779
): GraphQLServerResponse {
78-
val dataLoaderRegistry = dataLoaderRegistryFactory.generate(graphQLContext)
7980
return when (graphQLRequest) {
8081
is GraphQLRequest -> {
81-
val batchGraphQLContext = graphQLContext + getBatchContext(1, dataLoaderRegistry)
82+
val (batchContext, dataLoaderInstrumentation) = getBatchContext(1)
83+
val batchGraphQLContext = graphQLContext + batchContext
84+
val dataLoaderRegistry = dataLoaderRegistryFactory.generate(batchGraphQLContext, dataLoaderInstrumentation)
8285
execute(graphQLRequest, batchGraphQLContext, dataLoaderRegistry)
8386
}
84-
8587
is GraphQLBatchRequest -> {
8688
if (graphQLRequest.containsMutation()) {
87-
val batchGraphQLContext = graphQLContext + getBatchContext(1, dataLoaderRegistry)
89+
val (batchContext, dataLoaderInstrumentation) = getBatchContext(1)
90+
val batchGraphQLContext = graphQLContext + batchContext
91+
val dataLoaderRegistry = dataLoaderRegistryFactory.generate(batchGraphQLContext, dataLoaderInstrumentation)
8892
executeSequentially(graphQLRequest, batchGraphQLContext, dataLoaderRegistry)
8993
} else {
90-
val batchGraphQLContext = graphQLContext + getBatchContext(graphQLRequest.requests.size, dataLoaderRegistry)
94+
val (batchContext, dataLoaderInstrumentation) = getBatchContext(graphQLRequest.requests.size)
95+
val batchGraphQLContext = graphQLContext + batchContext
96+
val dataLoaderRegistry = dataLoaderRegistryFactory.generate(batchGraphQLContext, dataLoaderInstrumentation)
9197
executeConcurrently(graphQLRequest, batchGraphQLContext, dataLoaderRegistry)
9298
}
9399
}
@@ -134,17 +140,18 @@ open class GraphQLRequestHandler(
134140
return GraphQLBatchResponse(responses)
135141
}
136142

137-
private fun getBatchContext(
138-
batchSize: Int,
139-
dataLoaderRegistry: KotlinDataLoaderRegistry
140-
): Map<*, Any> =
141-
when (batchDataLoaderInstrumentationType) {
142-
DataLoaderSyncExecutionExhaustedInstrumentation::class.java -> mapOf(
143-
KotlinDataLoaderRegistry::class to dataLoaderRegistry,
144-
SyncExecutionExhaustedState::class to SyncExecutionExhaustedState(batchSize, dataLoaderRegistry)
145-
)
146-
else -> emptyMap<Any, Any>()
143+
private fun getBatchContext(batchSize: Int): Pair<GraphQLContext, DataLoaderInstrumentation?> {
144+
return when (batchDataLoaderInstrumentationType) {
145+
DataLoaderSyncExecutionExhaustedInstrumentation::class.java -> {
146+
val syncExecutionExhaustedState = SyncExecutionExhaustedState(batchSize)
147+
GraphQLContext.of(
148+
mapOf(SyncExecutionExhaustedState::class to syncExecutionExhaustedState)
149+
) to DataLoaderInstrumentationForSyncExecutionExhausted(syncExecutionExhaustedState)
150+
}
151+
152+
else -> GraphQLContext.getDefault() to null
147153
}
154+
}
148155

149156
/**
150157
* Execute a GraphQL subscription operation in a non-blocking fashion.

servers/graphql-kotlin-spring-server/src/main/kotlin/com/expediagroup/graphql/server/spring/GraphQLExecutionConfiguration.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package com.expediagroup.graphql.server.spring
1919
import com.expediagroup.graphql.generator.execution.KotlinDataFetcherFactoryProvider
2020
import com.expediagroup.graphql.dataloader.KotlinDataLoaderRegistryFactory
2121
import com.expediagroup.graphql.dataloader.KotlinDataLoader
22-
import com.expediagroup.graphql.dataloader.DataLoaderDependantsStateInstrumentation
22+
import com.expediagroup.graphql.dataloader.KotlinDataLoaderInstrumentation
2323
import com.expediagroup.graphql.server.spring.execution.SpringKotlinDataFetcherFactoryProvider
2424
import graphql.execution.DataFetcherExceptionHandler
2525
import graphql.execution.SimpleDataFetcherExceptionHandler
@@ -63,7 +63,7 @@ class GraphQLExecutionConfiguration {
6363
instrumentations.addAll(it)
6464
}
6565
if (config.batching.enabled) {
66-
instrumentations.add(DataLoaderDependantsStateInstrumentation())
66+
instrumentations.add(KotlinDataLoaderInstrumentation())
6767
}
6868
return KotlinDataLoaderRegistryFactory(
6969
dataLoaders.orElse(emptyList()),

0 commit comments

Comments
 (0)