Skip to content

Commit a0a8bad

Browse files
samuelAndalonsamvazquez
andauthored
feat: DataLoader Sync Execution Exhaustion Instrumentation (#1419)
* feat: sync execution exhaustion * feat: make extension fun internal * feat: specific import * feat: just verify independent dataloader statistics * feat: update README Co-authored-by: samvazquez <[email protected]>
1 parent fda83fc commit a0a8bad

File tree

17 files changed

+1327
-117
lines changed

17 files changed

+1327
-117
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Visit our [documentation site](https://expediagroup.github.io/graphql-kotlin) fo
1313

1414
* [clients](/clients) - Lightweight GraphQL Kotlin HTTP clients based on Ktor HTTP client and Spring WebClient
1515
* [examples](/examples) - Example apps that use graphql-kotlin libraries to test and demonstrate usages
16+
* [executions](/executions) - Custom instrumentations for a GraphQL operation
1617
* [generator](/generator) - Code-First schema generator and extensions to build Apollo Federation schemas
1718
* [plugins](/plugins) - Gradle and Maven plugins
1819
* [servers](/servers) - Common and library specific modules for running a GraphQL server

executions/graphql-kotlin-dataloader-instrumentation/README.md

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,40 +33,43 @@ implementation("com.expediagroup:graphql-kotlin-dataloader-instrumentation:$late
3333
## Use it
3434

3535
When creating your `GraphQL` instance make sure to include either
36-
`DataLoaderLevelDispatchedInstrumentation` or `DataLoaderSyncExhaustionInstrumentation`.
36+
`DataLoaderLevelDispatchedInstrumentation` or `DataLoaderSyncExecutionExhaustedInstrumentation`.
3737

3838
```kotlin
3939
GraphQL
40-
.instrumentation(DataLoaderSyncExhaustionInstrumentation())
40+
.instrumentation(DataLoaderSyncExecutionExhaustedInstrumentation())
4141
// configure schema, type wiring, etc.
4242
.build()
4343
```
4444

45-
When ready to execute an operation or operations make sure to create a single instance of `KotlinDataLoaderRegistry`
46-
and an instance of either `ExecutionLevelInstrumentationState` or `SyncExhaustionInstrumentationState`
47-
and store them in the `graphQLContext`.
45+
When ready to execute an operation or operations create a `GraphQLContext` instance with the following entries:
46+
1. An instance of `KotlinDataLoaderRegistry`
47+
2. Either:
48+
- An instance of `ExecutionLevelDispatchedState` if you choose `DataLoaderLevelDispatchedInstrumentation`.
49+
- An instance of `SyncExecutionExhaustedState` if you choose `DataLoaderSyncExecutionExhaustedInstrumentation`.
50+
4851

4952
```kotlin
5053
val queries = [
5154
"""
52-
query Query1 {
53-
nasa {
54-
astronaut(id: 1)
55-
}
56-
}
55+
query Query1 {
56+
nasa {
57+
astronaut(id: 1)
58+
}
59+
}
5760
""",
5861
"""
59-
query Query1 {
60-
nasa {
61-
astronaut(id: 1)
62-
}
63-
}
62+
query Query1 {
63+
nasa {
64+
astronaut(id: 2)
65+
}
66+
}
6467
"""
6568
]
6669

6770
val graphQLContext = mapOf(
6871
KotlinDataLoaderRegistry::class to kotlinDataLoaderRegistry,
69-
SyncExhaustionInstrumentationState::class to SyncExhaustionInstrumentationState(
72+
SyncExecutionExhaustedState::class to SyncExecutionExhaustedState(
7073
queries.size,
7174
kotlinDataLoaderRegistry
7275
)
@@ -81,17 +84,17 @@ val result2 = graphQL.executeAsync(executionInput2)
8184

8285
- `DataLoaderLevelDispatchedInstrumentation` will dispatch the `KotlinDataLoaderRegistry` instance when
8386
a certain level of all executionInputs was dispatched (all DataFetchers were invoked).
84-
- `DataLoaderSyncExhaustionInstrumentation` will dispatch the `KotlinDataLoaderRegistry` instance when
87+
- `DataLoaderSyncExecutionExhaustedInstrumentation` will dispatch the `KotlinDataLoaderRegistry` instance when
8588
the synchronous execution of an operation exhausted (synchronous execution will be exhausted when all data fetchers
86-
of all paths executed up until a scalar leaf, or a [CompletableFuture]).
89+
of all paths executed up until a scalar leaf, or a `CompletableFuture`).
8790

8891
This way even if you are executing 2 separate operations you can still batch operations triggered from a DataFetcher.
8992

9093
### Usage in DataFetcher
9194

9295
You can use the `DataFetchingEnvironment` which is passed to each
9396
`DataFetcher` and invoke the `getDataLoaderFromContext(dataLoaderName)` method which will access to the `KotlinDataLoaderRegistry`
94-
in the `GraphQLContext` and provide the `DataLaoder` that you specified as `dataLoaderName` argument.
97+
in the `GraphQLContext` and provide the `DataLoader` that you specified as `dataLoaderName` argument.
9598

9699
```kotlin
97100
class AstronautService {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2022 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.KotlinDataLoaderRegistry
20+
import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.execution.AbstractSyncExecutionExhaustedInstrumentation
21+
import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.execution.OnSyncExecutionExhaustedCallback
22+
import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.execution.SyncExecutionExhaustedInstrumentationParameters
23+
import graphql.ExecutionInput
24+
import graphql.GraphQLContext
25+
import graphql.execution.instrumentation.Instrumentation
26+
import graphql.schema.DataFetcher
27+
import org.dataloader.DataLoader
28+
import java.util.concurrent.CompletableFuture
29+
30+
/**
31+
* Custom GraphQL [Instrumentation] that will dispatch all [DataLoader]s inside a [KotlinDataLoaderRegistry]
32+
* when the synchronous execution of all [ExecutionInput] sharing a [GraphQLContext] was exhausted.
33+
*
34+
* A Synchronous Execution is considered Exhausted when all [DataFetcher]s of all paths were executed up until
35+
* an scalar leaf or a [DataFetcher] that returns a [CompletableFuture]
36+
*/
37+
class DataLoaderSyncExecutionExhaustedInstrumentation : AbstractSyncExecutionExhaustedInstrumentation() {
38+
override fun getOnSyncExecutionExhaustedCallback(
39+
parameters: SyncExecutionExhaustedInstrumentationParameters
40+
): OnSyncExecutionExhaustedCallback = {
41+
parameters
42+
.executionContext
43+
.graphQLContext.get<KotlinDataLoaderRegistry>(KotlinDataLoaderRegistry::class)
44+
?.dispatchAll()
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2022 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.execution
18+
19+
import com.expediagroup.graphql.dataloader.instrumentation.NoOpExecutionStrategyInstrumentationContext
20+
import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.state.SyncExecutionExhaustedState
21+
import graphql.ExecutionInput
22+
import graphql.ExecutionResult
23+
import graphql.GraphQLContext
24+
import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext
25+
import graphql.execution.instrumentation.Instrumentation
26+
import graphql.execution.instrumentation.InstrumentationContext
27+
import graphql.execution.instrumentation.SimpleInstrumentation
28+
import graphql.execution.instrumentation.SimpleInstrumentationContext
29+
import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters
30+
import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters
31+
import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters
32+
33+
/**
34+
* typealias that represents the signature of a callback that will be executed when sync execution is exhausted
35+
*/
36+
internal typealias OnSyncExecutionExhaustedCallback = (List<ExecutionInput>) -> Unit
37+
38+
/**
39+
* Custom GraphQL [Instrumentation] that calculate the synchronous execution exhaustion
40+
* of all GraphQL operations sharing the same [GraphQLContext]
41+
*/
42+
abstract class AbstractSyncExecutionExhaustedInstrumentation : SimpleInstrumentation() {
43+
/**
44+
* This is invoked each time instrumentation attempts to calculate exhaustion state, this can be called from either
45+
* `beginFieldField.dispatch` or `beginFieldFetch.complete`.
46+
*
47+
* @param parameters contains information of which [ExecutionInput] caused the calculation
48+
* @return [OnSyncExecutionExhaustedCallback] to invoke when the synchronous execution of all operations was exhausted
49+
*/
50+
abstract fun getOnSyncExecutionExhaustedCallback(
51+
parameters: SyncExecutionExhaustedInstrumentationParameters
52+
): OnSyncExecutionExhaustedCallback
53+
54+
override fun beginExecuteOperation(
55+
parameters: InstrumentationExecuteOperationParameters
56+
): InstrumentationContext<ExecutionResult> =
57+
parameters.executionContext
58+
.graphQLContext.get<SyncExecutionExhaustedState>(SyncExecutionExhaustedState::class)
59+
?.beginExecuteOperation(parameters)
60+
?: SimpleInstrumentationContext.noOp()
61+
62+
override fun beginExecutionStrategy(
63+
parameters: InstrumentationExecutionStrategyParameters
64+
): ExecutionStrategyInstrumentationContext =
65+
parameters.executionContext
66+
.graphQLContext.get<SyncExecutionExhaustedState>(SyncExecutionExhaustedState::class)
67+
?.beginExecutionStrategy(parameters)
68+
?: NoOpExecutionStrategyInstrumentationContext
69+
70+
override fun beginFieldFetch(
71+
parameters: InstrumentationFieldFetchParameters
72+
): InstrumentationContext<Any> =
73+
parameters.executionContext
74+
.graphQLContext.get<SyncExecutionExhaustedState>(SyncExecutionExhaustedState::class)
75+
?.beginFieldFetch(
76+
parameters,
77+
this.getOnSyncExecutionExhaustedCallback(
78+
SyncExecutionExhaustedInstrumentationParameters(parameters.executionContext)
79+
)
80+
)
81+
?: SimpleInstrumentationContext.noOp()
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2022 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.execution
18+
19+
import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.DataLoaderSyncExecutionExhaustedInstrumentation
20+
import graphql.execution.ExecutionContext
21+
22+
/**
23+
* Hold information that will be provided to an instance of [DataLoaderSyncExecutionExhaustedInstrumentation]
24+
*/
25+
data class SyncExecutionExhaustedInstrumentationParameters(
26+
val executionContext: ExecutionContext
27+
)

0 commit comments

Comments
 (0)