Skip to content

Commit fda83fc

Browse files
samuelAndalonsamvazquez
andauthored
feat: rename instrumentation module (#1418)
* feat: rename instrumentation module * update README with proper module name * feat update instrumentation name in README * feat: update kdocs Co-authored-by: samvazquez <[email protected]>
1 parent 62760c2 commit fda83fc

32 files changed

+674
-361
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# GraphQL Kotlin Data Loader Instrumentation
2+
[![Maven Central](https://img.shields.io/maven-central/v/com.expediagroup/graphql-kotlin-transaction-batcher-instrumentation.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22com.expediagroup%22%20AND%20a:%22graphql-kotlin-transaction-batcher-instrumentation%22)
3+
[![Javadocs](https://img.shields.io/maven-central/v/com.expediagroup/graphql-kotlin-transaction-batcher-instrumentation.svg?label=javadoc&colorB=brightgreen)](https://www.javadoc.io/doc/com.expediagroup/graphql-kotlin-transaction-batcher-instrumentation)
4+
5+
`graphql-kotlin-dataloader-instrumentation` is set of custom instrumentations that will signal when is the right moment
6+
to dispatch a `KotlinDataLoaderRegistry` located in the `GraphQLContext`.
7+
8+
This instrumentation follows the same approach of the [DataLoaderDispatcherInstrumentation](https://github.com/graphql-java/graphql-java/blob/master/src/main/java/graphql/execution/instrumentation/dataloader/DataLoaderDispatcherInstrumentation.java).
9+
10+
The main difference is that regular `Instrumentation`s are applied by a single `ExecutionInput` aka GraphQL Operation,
11+
whereas these custom instrumentations applies across a number of operations and stores its state in the `GraphQLContext`.
12+
13+
## Install it
14+
15+
Using a JVM dependency manager, link `graphql-kotlin-dataloader-instrumentation` to your project.
16+
17+
With Maven:
18+
19+
```xml
20+
<dependency>
21+
<groupId>com.expediagroup</groupId>
22+
<artifactId>graphql-kotlin-dataloader-instrumentation</artifactId>
23+
<version>${latestVersion}</version>
24+
</dependency>
25+
```
26+
27+
With Gradle (example using kts):
28+
29+
```kotlin
30+
implementation("com.expediagroup:graphql-kotlin-dataloader-instrumentation:$latestVersion")
31+
```
32+
33+
## Use it
34+
35+
When creating your `GraphQL` instance make sure to include either
36+
`DataLoaderLevelDispatchedInstrumentation` or `DataLoaderSyncExhaustionInstrumentation`.
37+
38+
```kotlin
39+
GraphQL
40+
.instrumentation(DataLoaderSyncExhaustionInstrumentation())
41+
// configure schema, type wiring, etc.
42+
.build()
43+
```
44+
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`.
48+
49+
```kotlin
50+
val queries = [
51+
"""
52+
query Query1 {
53+
nasa {
54+
astronaut(id: 1)
55+
}
56+
}
57+
""",
58+
"""
59+
query Query1 {
60+
nasa {
61+
astronaut(id: 1)
62+
}
63+
}
64+
"""
65+
]
66+
67+
val graphQLContext = mapOf(
68+
KotlinDataLoaderRegistry::class to kotlinDataLoaderRegistry,
69+
SyncExhaustionInstrumentationState::class to SyncExhaustionInstrumentationState(
70+
queries.size,
71+
kotlinDataLoaderRegistry
72+
)
73+
)
74+
75+
val executionInput1 = ExecutionInput.newExecutionInput(queries[0]).graphQLContext(graphQLContext).build()
76+
val executionInput2 = ExecutionInput.newExecutionInput(queries[1]).graphQLContext(graphQLContext).build()
77+
78+
val result1 = graphQL.executeAsync(executionInput1)
79+
val result2 = graphQL.executeAsync(executionInput2)
80+
```
81+
82+
- `DataLoaderLevelDispatchedInstrumentation` will dispatch the `KotlinDataLoaderRegistry` instance when
83+
a certain level of all executionInputs was dispatched (all DataFetchers were invoked).
84+
- `DataLoaderSyncExhaustionInstrumentation` will dispatch the `KotlinDataLoaderRegistry` instance when
85+
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]).
87+
88+
This way even if you are executing 2 separate operations you can still batch operations triggered from a DataFetcher.
89+
90+
### Usage in DataFetcher
91+
92+
You can use the `DataFetchingEnvironment` which is passed to each
93+
`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.
95+
96+
```kotlin
97+
class AstronautService {
98+
fun getAstronaut(
99+
request: AstronautServiceRequest,
100+
environment: DataFetchingEnvironment
101+
): CompletableFuture<Astronaut> =
102+
environment
103+
.getDataLoaderFromContext<AstronautServiceRequest, Astronaut>("AstronautDataLoader")
104+
.load(request)
105+
}
106+
```
107+
108+
109+

executions/graphql-kotlin-transaction-loader-instrumentation/build.gradle.kts renamed to executions/graphql-kotlin-dataloader-instrumentation/build.gradle.kts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
description = "Transaction Loader Instrumentations"
1+
description = "Data Loader Instrumentations"
22

33
val junitVersion: String by project
44
val graphQLJavaVersion: String by project
@@ -13,3 +13,22 @@ dependencies {
1313
testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion")
1414
testImplementation("org.junit.jupiter:junit-jupiter-engine:$junitVersion")
1515
}
16+
17+
tasks {
18+
jacocoTestCoverageVerification {
19+
violationRules {
20+
rule {
21+
limit {
22+
counter = "INSTRUCTION"
23+
value = "COVEREDRATIO"
24+
minimum = "0.93".toBigDecimal()
25+
}
26+
limit {
27+
counter = "BRANCH"
28+
value = "COVEREDRATIO"
29+
minimum = "0.70".toBigDecimal()
30+
}
31+
}
32+
}
33+
}
34+
}

executions/graphql-kotlin-transaction-loader-instrumentation/src/main/kotlin/com/expediagroup/graphql/transactionbatcher/instrumentation/NoOpExecutionStrategyInstrumentationContext.kt renamed to executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/NoOpExecutionStrategyInstrumentationContext.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package com.expediagroup.graphql.transactionbatcher.instrumentation
17+
package com.expediagroup.graphql.dataloader.instrumentation
1818

1919
import graphql.ExecutionResult
2020
import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext

executions/graphql-kotlin-transaction-loader-instrumentation/src/main/kotlin/com/expediagroup/graphql/transactionbatcher/instrumentation/exceptions/MissingDataLoaderRegistryException.kt renamed to executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/exceptions/MissingDataLoaderRegistryException.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package com.expediagroup.graphql.transactionbatcher.instrumentation.exceptions
17+
package com.expediagroup.graphql.dataloader.instrumentation.exceptions
1818

1919
import graphql.GraphQLContext
2020
import org.dataloader.DataLoaderRegistry
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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.exceptions
18+
19+
import com.expediagroup.graphql.dataloader.KotlinDataLoaderRegistry
20+
import graphql.GraphQLContext
21+
22+
/**
23+
* Thrown when an instance of [KotlinDataLoaderRegistry] does not exists in the [GraphQLContext].
24+
*/
25+
class MissingKotlinDataLoaderRegistryException : RuntimeException("KotlinDataLoaderRegistry instance not found in the GraphQLContext")

executions/graphql-kotlin-transaction-loader-instrumentation/src/main/kotlin/com/expediagroup/graphql/transactionbatcher/instrumentation/extensions/DataFetchingEnvironmentExtensions.kt renamed to executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/extensions/DataFetchingEnvironmentExtensions.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,21 @@
1414
* limitations under the License.
1515
*/
1616

17-
package com.expediagroup.graphql.transactionbatcher.instrumentation.extensions
17+
package com.expediagroup.graphql.dataloader.instrumentation.extensions
1818

1919
import com.expediagroup.graphql.dataloader.KotlinDataLoaderRegistry
20-
import com.expediagroup.graphql.transactionbatcher.instrumentation.exceptions.MissingDataLoaderRegistryException
20+
import com.expediagroup.graphql.dataloader.instrumentation.exceptions.MissingKotlinDataLoaderRegistryException
2121
import graphql.GraphQLContext
2222
import graphql.schema.DataFetchingEnvironment
2323
import org.dataloader.DataLoader
24-
import org.dataloader.DataLoaderRegistry
2524

2625
/**
27-
* get an instance of [DataLoaderRegistry] from the [GraphQLContext]
26+
* get an instance of [KotlinDataLoaderRegistry] from the [GraphQLContext]
2827
* @return [KotlinDataLoaderRegistry] instance
29-
* @throws [MissingDataLoaderRegistryException] if there is not a [KotlinDataLoaderRegistry] instance in the [GraphQLContext]
28+
* @throws [MissingKotlinDataLoaderRegistryException] if there is not a [KotlinDataLoaderRegistry] instance in the [GraphQLContext]
3029
*/
3130
fun <K, V> DataFetchingEnvironment.getDataLoaderFromContext(key: String): DataLoader<K, V> =
3231
this.graphQlContext
3332
.get<KotlinDataLoaderRegistry>(KotlinDataLoaderRegistry::class)
3433
?.getDataLoader(key)
35-
?: throw MissingDataLoaderRegistryException()
34+
?: throw MissingKotlinDataLoaderRegistryException()

executions/graphql-kotlin-transaction-loader-instrumentation/src/main/kotlin/com/expediagroup/graphql/transactionbatcher/instrumentation/extensions/ExecutionContextExtensions.kt renamed to executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/extensions/ExecutionContextExtensions.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package com.expediagroup.graphql.transactionbatcher.instrumentation.extensions
17+
package com.expediagroup.graphql.dataloader.instrumentation.extensions
1818

1919
import graphql.analysis.QueryTraverser
2020
import graphql.analysis.QueryVisitorFieldEnvironment

executions/graphql-kotlin-transaction-loader-instrumentation/src/main/kotlin/com/expediagroup/graphql/transactionbatcher/instrumentation/extensions/FieldValueInfosExtensions.kt renamed to executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/extensions/FieldValueInfosExtensions.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package com.expediagroup.graphql.transactionbatcher.instrumentation.extensions
17+
package com.expediagroup.graphql.dataloader.instrumentation.extensions
1818

1919
import graphql.execution.FieldValueInfo
2020

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.level
18+
19+
import com.expediagroup.graphql.dataloader.KotlinDataLoaderRegistry
20+
import com.expediagroup.graphql.dataloader.instrumentation.level.execution.AbstractExecutionLevelDispatchedInstrumentation
21+
import com.expediagroup.graphql.dataloader.instrumentation.level.execution.ExecutionLevelDispatchedInstrumentationParameters
22+
import com.expediagroup.graphql.dataloader.instrumentation.level.execution.OnLevelDispatchedCallback
23+
import com.expediagroup.graphql.dataloader.instrumentation.level.state.Level
24+
import graphql.ExecutionInput
25+
import graphql.GraphQLContext
26+
import graphql.execution.instrumentation.Instrumentation
27+
import graphql.schema.DataFetcher
28+
import org.dataloader.DataLoader
29+
30+
/**
31+
* Custom GraphQL [Instrumentation] that will dispatch all [DataLoader]s inside a [KotlinDataLoaderRegistry]
32+
* when certain [Level] is dispatched for all [ExecutionInput] sharing a [GraphQLContext]
33+
*
34+
* A level is considered Dispatched when all [DataFetcher]s of a particular level of all [ExecutionInput]s
35+
* were dispatched
36+
*/
37+
class DataLoaderLevelDispatchedInstrumentation : AbstractExecutionLevelDispatchedInstrumentation() {
38+
override fun getOnLevelDispatchedCallback(
39+
parameters: ExecutionLevelDispatchedInstrumentationParameters
40+
): OnLevelDispatchedCallback = { _, _ ->
41+
parameters
42+
.executionContext
43+
.graphQLContext.get<KotlinDataLoaderRegistry>(KotlinDataLoaderRegistry::class)
44+
?.dispatchAll()
45+
}
46+
}

executions/graphql-kotlin-transaction-loader-instrumentation/src/main/kotlin/com/expediagroup/graphql/transactionbatcher/instrumentation/level/execution/AbstractExecutionLevelInstrumentation.kt renamed to executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/execution/AbstractExecutionLevelDispatchedInstrumentation.kt

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414
* limitations under the License.
1515
*/
1616

17-
package com.expediagroup.graphql.transactionbatcher.instrumentation.level.execution
17+
package com.expediagroup.graphql.dataloader.instrumentation.level.execution
1818

19-
import com.expediagroup.graphql.transactionbatcher.instrumentation.NoOpExecutionStrategyInstrumentationContext
20-
import com.expediagroup.graphql.transactionbatcher.instrumentation.level.state.ExecutionLevelInstrumentationState
19+
import com.expediagroup.graphql.dataloader.instrumentation.NoOpExecutionStrategyInstrumentationContext
20+
import com.expediagroup.graphql.dataloader.instrumentation.level.state.ExecutionLevelDispatchedState
21+
import com.expediagroup.graphql.dataloader.instrumentation.level.state.Level
22+
import graphql.ExecutionInput
2123
import graphql.ExecutionResult
2224
import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext
2325
import graphql.execution.instrumentation.InstrumentationContext
@@ -28,29 +30,44 @@ import graphql.execution.instrumentation.parameters.InstrumentationExecutionStra
2830
import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters
2931
import graphql.schema.DataFetcher
3032

33+
/**
34+
* Represents the signature of a callback that will be executed when a [Level] is dispatched
35+
*/
36+
internal typealias OnLevelDispatchedCallback = (Level, List<ExecutionInput>) -> Unit
3137
/**
3238
* Custom GraphQL [graphql.execution.instrumentation.Instrumentation] that calculate the state of executions
3339
* of all queries sharing the same GraphQLContext map
3440
*/
35-
abstract class AbstractExecutionLevelInstrumentation : SimpleInstrumentation(), ExecutionLevelInstrumentation {
41+
abstract class AbstractExecutionLevelDispatchedInstrumentation : SimpleInstrumentation() {
42+
/**
43+
* This is invoked each time instrumentation attempts to calculate a level dispatched state, this can be called from either
44+
* `beginFieldField` or `beginExecutionStrategy`.
45+
*
46+
* @param parameters contains information of which [ExecutionInput] caused the calculation and from which hook
47+
* @return [OnLevelDispatchedCallback] to invoke a method when a certain level of all operations dispatched
48+
* like `onDispatched`
49+
*/
50+
abstract fun getOnLevelDispatchedCallback(
51+
parameters: ExecutionLevelDispatchedInstrumentationParameters
52+
): OnLevelDispatchedCallback
3653

3754
override fun beginExecuteOperation(
3855
parameters: InstrumentationExecuteOperationParameters
3956
): InstrumentationContext<ExecutionResult> =
4057
parameters.executionContext
41-
.graphQLContext.get<ExecutionLevelInstrumentationState>(ExecutionLevelInstrumentationState::class)
58+
.graphQLContext.get<ExecutionLevelDispatchedState>(ExecutionLevelDispatchedState::class)
4259
?.beginExecuteOperation(parameters)
4360
?: SimpleInstrumentationContext.noOp()
4461

4562
override fun beginExecutionStrategy(
4663
parameters: InstrumentationExecutionStrategyParameters
4764
): ExecutionStrategyInstrumentationContext =
4865
parameters.executionContext
49-
.graphQLContext.get<ExecutionLevelInstrumentationState>(ExecutionLevelInstrumentationState::class)
66+
.graphQLContext.get<ExecutionLevelDispatchedState>(ExecutionLevelDispatchedState::class)
5067
?.beginExecutionStrategy(
5168
parameters,
52-
this.calculateLevelState(
53-
ExecutionLevelInstrumentationParameters(
69+
this.getOnLevelDispatchedCallback(
70+
ExecutionLevelDispatchedInstrumentationParameters(
5471
parameters.executionContext,
5572
ExecutionLevelCalculationSource.EXECUTION_STRATEGY
5673
)
@@ -62,11 +79,11 @@ abstract class AbstractExecutionLevelInstrumentation : SimpleInstrumentation(),
6279
parameters: InstrumentationFieldFetchParameters
6380
): InstrumentationContext<Any> =
6481
parameters.executionContext
65-
.graphQLContext.get<ExecutionLevelInstrumentationState>(ExecutionLevelInstrumentationState::class)
82+
.graphQLContext.get<ExecutionLevelDispatchedState>(ExecutionLevelDispatchedState::class)
6683
?.beginFieldFetch(
6784
parameters,
68-
this.calculateLevelState(
69-
ExecutionLevelInstrumentationParameters(
85+
this.getOnLevelDispatchedCallback(
86+
ExecutionLevelDispatchedInstrumentationParameters(
7087
parameters.executionContext,
7188
ExecutionLevelCalculationSource.FIELD_FETCH
7289
)
@@ -79,7 +96,7 @@ abstract class AbstractExecutionLevelInstrumentation : SimpleInstrumentation(),
7996
parameters: InstrumentationFieldFetchParameters
8097
): DataFetcher<*> =
8198
parameters.executionContext
82-
.graphQLContext.get<ExecutionLevelInstrumentationState>(ExecutionLevelInstrumentationState::class)
99+
.graphQLContext.get<ExecutionLevelDispatchedState>(ExecutionLevelDispatchedState::class)
83100
?.instrumentDataFetcher(dataFetcher, parameters)
84101
?: dataFetcher
85102
}

0 commit comments

Comments
 (0)