From bce5bc46590c8a12a1c5a0a70970202660eea4bb Mon Sep 17 00:00:00 2001 From: Samuel Vazquez Date: Mon, 6 May 2024 15:26:15 -0700 Subject: [PATCH] graphql java 22 update (#1967) ### :pencil: Description waiting for an official release of this PR https://github.com/graphql-java/graphql-java/pull/3571 UPDATE: next graphql-java update will be on July, https://github.com/graphql-java/graphql-java/milestone/69 they helped us releasing from this branch https://github.com/graphql-java/graphql-java/pull/3571 which will unblock us. https://github.com/graphql-java/graphql-java/releases/tag/v22.0 --- .../TrackTimesInvokedInstrumentation.kt | 5 +- .../cache/AutomaticPersistedQueriesCache.kt | 12 - .../AutomaticPersistedQueriesProvider.kt | 14 -- .../extensions/CompletableFutureExtensions.kt | 9 +- ...ataLoaderLevelDispatchedInstrumentation.kt | 45 ---- ...ExecutionLevelDispatchedInstrumentation.kt | 105 --------- ...evelDispatchedInstrumentationParameters.kt | 32 --- .../level/state/ExecutionBatchState.kt | 156 ------------- .../state/ExecutionLevelDispatchedState.kt | 211 ------------------ .../instrumentation/level/state/Level.kt | 40 ---- .../level/state/ManualDataFetcher.kt | 44 ---- .../state/ManuallyCompletableDataFetcher.kt | 52 ----- .../ManuallyCompletableLightDataFetcher.kt | 68 ------ ...ctSyncExecutionExhaustedInstrumentation.kt | 24 +- .../state/ExecutionBatchState.kt | 5 +- .../state/ExecutionStrategyState.kt | 9 +- .../state/SyncExecutionExhaustedState.kt | 24 +- .../fixture/AstronautGraphQL.kt | 7 +- .../DataLoaderInstrumentationStrategy.kt | 2 +- .../instrumentation/fixture/ProductGraphQL.kt | 5 - ...oaderLevelDispatchedInstrumentationTest.kt | 196 ---------------- ...ncExecutionExhaustedInstrumentationTest.kt | 6 +- .../build.gradle.kts | 5 +- .../federation/data/TestResolvers.kt | 4 +- .../execution/ServiceQueryResolverTest.kt | 4 +- .../FlowSubscriptionExecutionStrategy.kt | 18 +- gradle/libs.versions.toml | 4 +- .../generator/GraphQLClientGenerator.kt | 10 +- .../graphql/server/ktor/GraphQL.kt | 6 +- .../server/ktor/GraphQLConfiguration.kt | 4 +- .../server/execution/GraphQLRequestHandler.kt | 17 +- .../extensions/instrumentationExtensions.kt | 4 +- .../execution/GraphQLRequestHandlerTest.kt | 9 +- .../GraphQLWebSocketServerTest.kt | 4 +- .../spring/GraphQLConfigurationProperties.kt | 3 +- .../spring/GraphQLSchemaConfiguration.kt | 6 +- .../instrumentation/InstrumentationIT.kt | 11 +- ...oader-level-dispatched-instrumentation.png | Bin 139009 -> 0 bytes .../data-loader-instrumentation.mdx | 95 +------- .../server/spring-server/spring-properties.md | 2 +- 40 files changed, 102 insertions(+), 1175 deletions(-) delete mode 100644 executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/DataLoaderLevelDispatchedInstrumentation.kt delete mode 100644 executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/execution/AbstractExecutionLevelDispatchedInstrumentation.kt delete mode 100644 executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/execution/ExecutionLevelDispatchedInstrumentationParameters.kt delete mode 100644 executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ExecutionBatchState.kt delete mode 100644 executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ExecutionLevelDispatchedState.kt delete mode 100644 executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/Level.kt delete mode 100644 executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ManualDataFetcher.kt delete mode 100644 executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ManuallyCompletableDataFetcher.kt delete mode 100644 executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ManuallyCompletableLightDataFetcher.kt delete mode 100644 executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/DataLoaderLevelDispatchedInstrumentationTest.kt delete mode 100644 website/docs/assets/data-loader-level-dispatched-instrumentation.png diff --git a/examples/server/spring-server/src/main/kotlin/com/expediagroup/graphql/examples/server/spring/instrumentation/TrackTimesInvokedInstrumentation.kt b/examples/server/spring-server/src/main/kotlin/com/expediagroup/graphql/examples/server/spring/instrumentation/TrackTimesInvokedInstrumentation.kt index 53a1bba7e1..a7cb8c2d6c 100644 --- a/examples/server/spring-server/src/main/kotlin/com/expediagroup/graphql/examples/server/spring/instrumentation/TrackTimesInvokedInstrumentation.kt +++ b/examples/server/spring-server/src/main/kotlin/com/expediagroup/graphql/examples/server/spring/instrumentation/TrackTimesInvokedInstrumentation.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Expedia, Inc + * Copyright 2024 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import graphql.execution.instrumentation.InstrumentationContext import graphql.execution.instrumentation.InstrumentationState import graphql.execution.instrumentation.SimpleInstrumentationContext import graphql.execution.instrumentation.SimplePerformantInstrumentation +import graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters import org.slf4j.LoggerFactory @@ -37,7 +38,7 @@ class TrackTimesInvokedInstrumentation : SimplePerformantInstrumentation() { private val logger = LoggerFactory.getLogger(TrackTimesInvokedInstrumentation::class.java) - override fun createState(): InstrumentationState = TrackTimesInvokedInstrumenationState() + override fun createState(parameters: InstrumentationCreateStateParameters): InstrumentationState = TrackTimesInvokedInstrumenationState() override fun beginFieldFetch(parameters: InstrumentationFieldFetchParameters, state: InstrumentationState?): InstrumentationContext { if (parameters.field.getDirective(TRACK_TIMES_INVOKED_DIRECTIVE_NAME) != null) { diff --git a/executions/graphql-kotlin-automatic-persisted-queries/src/main/kotlin/com/expediagroup/graphql/apq/cache/AutomaticPersistedQueriesCache.kt b/executions/graphql-kotlin-automatic-persisted-queries/src/main/kotlin/com/expediagroup/graphql/apq/cache/AutomaticPersistedQueriesCache.kt index 0ba92960dd..ca8aa1fb84 100644 --- a/executions/graphql-kotlin-automatic-persisted-queries/src/main/kotlin/com/expediagroup/graphql/apq/cache/AutomaticPersistedQueriesCache.kt +++ b/executions/graphql-kotlin-automatic-persisted-queries/src/main/kotlin/com/expediagroup/graphql/apq/cache/AutomaticPersistedQueriesCache.kt @@ -23,18 +23,6 @@ import graphql.execution.preparsed.persisted.PersistedQueryCacheMiss import java.util.concurrent.CompletableFuture interface AutomaticPersistedQueriesCache : PersistedQueryCache { - - @Deprecated( - message = "deprecated in favor of async retrieval of PreparsedDocumentEntry", - replaceWith = ReplaceWith("getPersistedQueryDocumentAsync(persistedQueryId, executionInput, onCacheMiss)") - ) - override fun getPersistedQueryDocument( - persistedQueryId: Any, - executionInput: ExecutionInput, - onCacheMiss: PersistedQueryCacheMiss - ): PreparsedDocumentEntry = - getPersistedQueryDocumentAsync(persistedQueryId, executionInput, onCacheMiss).get() - override fun getPersistedQueryDocumentAsync( persistedQueryId: Any, executionInput: ExecutionInput, diff --git a/executions/graphql-kotlin-automatic-persisted-queries/src/main/kotlin/com/expediagroup/graphql/apq/provider/AutomaticPersistedQueriesProvider.kt b/executions/graphql-kotlin-automatic-persisted-queries/src/main/kotlin/com/expediagroup/graphql/apq/provider/AutomaticPersistedQueriesProvider.kt index ca67806bfa..0e6bb3662e 100644 --- a/executions/graphql-kotlin-automatic-persisted-queries/src/main/kotlin/com/expediagroup/graphql/apq/provider/AutomaticPersistedQueriesProvider.kt +++ b/executions/graphql-kotlin-automatic-persisted-queries/src/main/kotlin/com/expediagroup/graphql/apq/provider/AutomaticPersistedQueriesProvider.kt @@ -33,20 +33,6 @@ import java.util.function.Function class AutomaticPersistedQueriesProvider( private val cache: AutomaticPersistedQueriesCache ) : PreparsedDocumentProvider { - - @Deprecated( - "deprecated in favor of async retrieval of Document", - ReplaceWith("this.getDocumentAsync(executionInput, parseAndValidateFunction).get()") - ) - override fun getDocument( - executionInput: ExecutionInput, - parseAndValidateFunction: Function - ): PreparsedDocumentEntry = - this.getDocumentAsync( - executionInput, - parseAndValidateFunction - ).get() - override fun getDocumentAsync( executionInput: ExecutionInput, parseAndValidateFunction: Function diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/extensions/CompletableFutureExtensions.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/extensions/CompletableFutureExtensions.kt index 98df382ed5..02d2faab25 100644 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/extensions/CompletableFutureExtensions.kt +++ b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/extensions/CompletableFutureExtensions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Expedia, Inc + * Copyright 2024 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,6 @@ package com.expediagroup.graphql.dataloader.instrumentation.extensions import com.expediagroup.graphql.dataloader.KotlinDataLoaderRegistry import com.expediagroup.graphql.dataloader.instrumentation.exceptions.MissingInstrumentationStateException import com.expediagroup.graphql.dataloader.instrumentation.exceptions.MissingKotlinDataLoaderRegistryException -import com.expediagroup.graphql.dataloader.instrumentation.level.state.ExecutionLevelDispatchedState -import com.expediagroup.graphql.dataloader.instrumentation.level.state.Level import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.state.SyncExecutionExhaustedState import graphql.schema.DataFetchingEnvironment import org.dataloader.DataLoader @@ -38,11 +36,6 @@ fun CompletableFuture.dispatchIfNeeded( if (dataLoaderRegistry.dataLoadersInvokedOnDispatch()) { val cantContinueExecution = when { - environment.graphQlContext.hasKey(ExecutionLevelDispatchedState::class) -> { - environment - .graphQlContext.get(ExecutionLevelDispatchedState::class) - .allExecutionsDispatched(Level(environment.executionStepInfo.path.level)) - } environment.graphQlContext.hasKey(SyncExecutionExhaustedState::class) -> { environment .graphQlContext.get(SyncExecutionExhaustedState::class) diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/DataLoaderLevelDispatchedInstrumentation.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/DataLoaderLevelDispatchedInstrumentation.kt deleted file mode 100644 index 00b88c36a1..0000000000 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/DataLoaderLevelDispatchedInstrumentation.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2024 Expedia, Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.graphql.dataloader.instrumentation.level - -import com.expediagroup.graphql.dataloader.KotlinDataLoaderRegistry -import com.expediagroup.graphql.dataloader.instrumentation.level.execution.AbstractExecutionLevelDispatchedInstrumentation -import com.expediagroup.graphql.dataloader.instrumentation.level.execution.ExecutionLevelDispatchedInstrumentationParameters -import com.expediagroup.graphql.dataloader.instrumentation.level.execution.OnLevelDispatchedCallback -import com.expediagroup.graphql.dataloader.instrumentation.level.state.Level -import graphql.ExecutionInput -import graphql.GraphQLContext -import graphql.execution.instrumentation.Instrumentation -import graphql.schema.DataFetcher -import org.dataloader.DataLoader - -/** - * Custom GraphQL [Instrumentation] that will dispatch all [DataLoader]s inside a [KotlinDataLoaderRegistry] - * when certain [Level] is dispatched for all [ExecutionInput] sharing a [GraphQLContext] - * - * A level is considered Dispatched when all [DataFetcher]s of a particular level of all [ExecutionInput]s - * were dispatched - */ -class DataLoaderLevelDispatchedInstrumentation : AbstractExecutionLevelDispatchedInstrumentation() { - override fun getOnLevelDispatchedCallback( - parameters: ExecutionLevelDispatchedInstrumentationParameters - ): OnLevelDispatchedCallback = { _, _ -> - parameters.executionContext.executionInput - ?.dataLoaderRegistry - ?.dispatchAll() - } -} diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/execution/AbstractExecutionLevelDispatchedInstrumentation.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/execution/AbstractExecutionLevelDispatchedInstrumentation.kt deleted file mode 100644 index dfef0e2287..0000000000 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/execution/AbstractExecutionLevelDispatchedInstrumentation.kt +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2024 Expedia, Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.graphql.dataloader.instrumentation.level.execution - -import com.expediagroup.graphql.dataloader.instrumentation.extensions.isMutation -import com.expediagroup.graphql.dataloader.instrumentation.level.state.ExecutionLevelDispatchedState -import com.expediagroup.graphql.dataloader.instrumentation.level.state.Level -import graphql.ExecutionInput -import graphql.ExecutionResult -import graphql.execution.ExecutionContext -import graphql.execution.ExecutionId -import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext -import graphql.execution.instrumentation.InstrumentationContext -import graphql.execution.instrumentation.InstrumentationState -import graphql.execution.instrumentation.SimplePerformantInstrumentation -import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters -import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters -import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters -import graphql.schema.DataFetcher - -/** - * Represents the signature of a callback that will be executed when a [Level] is dispatched - */ -internal typealias OnLevelDispatchedCallback = (Level, List) -> Unit -/** - * Custom GraphQL [graphql.execution.instrumentation.Instrumentation] that calculate the state of executions - * of all queries sharing the same GraphQLContext map - */ -abstract class AbstractExecutionLevelDispatchedInstrumentation : SimplePerformantInstrumentation() { - /** - * This is invoked each time instrumentation attempts to calculate a level dispatched state, this can be called from either - * `beginFieldField` or `beginExecutionStrategy`. - * - * @param parameters contains information of which [ExecutionInput] caused the calculation and from which hook - * @return [OnLevelDispatchedCallback] to invoke a method when a certain level of all operations dispatched - * like `onDispatched` - */ - abstract fun getOnLevelDispatchedCallback( - parameters: ExecutionLevelDispatchedInstrumentationParameters - ): OnLevelDispatchedCallback - - override fun beginExecution( - parameters: InstrumentationExecutionParameters, - state: InstrumentationState? - ): InstrumentationContext? = - parameters.executionInput - ?.graphQLContext?.get(ExecutionLevelDispatchedState::class) - ?.beginExecution(parameters) - - override fun beginExecutionStrategy( - parameters: InstrumentationExecutionStrategyParameters, - state: InstrumentationState? - ): ExecutionStrategyInstrumentationContext? = - parameters.executionContext.takeUnless(ExecutionContext::isMutation) - ?.graphQLContext?.get(ExecutionLevelDispatchedState::class) - ?.beginExecutionStrategy( - parameters, - this.getOnLevelDispatchedCallback( - ExecutionLevelDispatchedInstrumentationParameters( - parameters.executionContext, - ExecutionLevelCalculationSource.EXECUTION_STRATEGY - ) - ) - ) - - override fun beginFieldFetch( - parameters: InstrumentationFieldFetchParameters, - state: InstrumentationState? - ): InstrumentationContext? = - parameters.executionContext.takeUnless(ExecutionContext::isMutation) - ?.graphQLContext?.get(ExecutionLevelDispatchedState::class) - ?.beginFieldFetch( - parameters, - this.getOnLevelDispatchedCallback( - ExecutionLevelDispatchedInstrumentationParameters( - parameters.executionContext, - ExecutionLevelCalculationSource.FIELD_FETCH - ) - ) - ) - - override fun instrumentDataFetcher( - dataFetcher: DataFetcher<*>, - parameters: InstrumentationFieldFetchParameters, - state: InstrumentationState? - ): DataFetcher<*> = - parameters.executionContext.takeUnless(ExecutionContext::isMutation) - ?.graphQLContext?.get(ExecutionLevelDispatchedState::class) - ?.instrumentDataFetcher(dataFetcher, parameters) - ?: dataFetcher -} diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/execution/ExecutionLevelDispatchedInstrumentationParameters.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/execution/ExecutionLevelDispatchedInstrumentationParameters.kt deleted file mode 100644 index a33e278b80..0000000000 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/execution/ExecutionLevelDispatchedInstrumentationParameters.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2022 Expedia, Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.graphql.dataloader.instrumentation.level.execution - -import graphql.execution.ExecutionContext - -/** - * Source of level dispatched state calculation - */ -enum class ExecutionLevelCalculationSource { EXECUTION_STRATEGY, FIELD_FETCH } - -/** - * Hold information related to from where the level dispatched state is being calculated - */ -data class ExecutionLevelDispatchedInstrumentationParameters( - val executionContext: ExecutionContext, - val calculationSource: ExecutionLevelCalculationSource -) diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ExecutionBatchState.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ExecutionBatchState.kt deleted file mode 100644 index f96e7745d5..0000000000 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ExecutionBatchState.kt +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2023 Expedia, Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.graphql.dataloader.instrumentation.level.state - -import graphql.schema.DataFetcher -import graphql.schema.LightDataFetcher - -enum class LevelState { NOT_DISPATCHED, DISPATCHED } - -/** - * Handle the state of an [graphql.ExecutionInput] - */ -class ExecutionBatchState { - private val levelsState: MutableMap = mutableMapOf() - - private val expectedFetches: MutableMap = mutableMapOf() - private val dispatchedFetches: MutableMap = mutableMapOf() - - private val expectedExecutionStrategies: MutableMap = mutableMapOf() - private val dispatchedExecutionStrategies: MutableMap = mutableMapOf() - - private val onFieldValueInfos: MutableMap = mutableMapOf() - - private val manuallyCompletableDataFetchers: MutableMap> = mutableMapOf() - - /** - * Initializes a level state in the [ExecutionBatchState] - * @param level to be initialized - */ - fun initializeLevelStateIfNeeded(level: Level) { - if (!this.contains(level)) { - levelsState[level] = LevelState.NOT_DISPATCHED - expectedFetches[level] = 0 - dispatchedFetches[level] = 0 - expectedExecutionStrategies[level] = if (level.isFirst()) 1 else 0 - dispatchedExecutionStrategies[level] = 0 - onFieldValueInfos[level] = 0 - manuallyCompletableDataFetchers[level] = mutableListOf() - } - } - - /** - * Check if the [ExecutionBatchState] contains a level - * - * @param level to check if his state is being calculated - * @return whether state contains the level - */ - fun contains(level: Level): Boolean = levelsState.containsKey(level) - - /** - * Increase fetches that this [ExecutionBatchState] is expecting - * - * @param level which level expects [count] of fetches - * @param count how many more fetches the [level] will expect - * @return total expected fetches - */ - fun increaseExpectedFetches(level: Level, count: Int): Int? = - expectedFetches.computeIfPresent(level) { _, currentCount -> currentCount + count } - - /** - * Increase dispatched fetches of this [ExecutionBatchState] - * - * @param level which level should increase dispatched fetches - * @return total dispatched fetches - */ - fun increaseDispatchedFetches(level: Level): Int? = - dispatchedFetches.computeIfPresent(level) { _, currentCount -> currentCount + 1 } - - /** - * Increase executionStrategies that this [ExecutionBatchState] is expecting - * - * @param level which level expects [count] of fetches - * @param count how many more executionStrategies the [level] will expect - * @return total expected executionStrategies - */ - fun increaseExpectedExecutionStrategies(level: Level, count: Int): Int? = - expectedExecutionStrategies.computeIfPresent(level) { _, currentCount -> currentCount + count } - - /** - * Increase dispatched executionStrategies of this [ExecutionBatchState] - * - * @param level which level should increase dispatched fetches - * @return total dispatched executionStrategies - */ - fun increaseDispatchedExecutionStrategies(level: Level): Int? = - dispatchedExecutionStrategies.computeIfPresent(level) { _, currentCount -> currentCount + 1 } - - /** - * Increase OnFieldValueInfos invocations of this [ExecutionBatchState] - * - * @param level which level should increase OnFieldValueInfos invocations - * @return total onFieldValueInfos invocations - */ - fun increaseOnFieldValueInfos(level: Level): Int? = - onFieldValueInfos.computeIfPresent(level) { _, currentCount -> currentCount + 1 } - - /** - * Instrument a dataFetcher to modify his runtime behavior to manually complete the returned CompletableFuture - * - * @param level which level the [dataFetcher] belongs - * @param dataFetcher to be instrumented - * @return instrumented dataFetcher - */ - fun toManuallyCompletableDataFetcher(level: Level, dataFetcher: DataFetcher<*>): ManualDataFetcher { - val manualDataFetcher = when (dataFetcher) { - is LightDataFetcher<*> -> ManuallyCompletableLightDataFetcher(dataFetcher) - else -> ManuallyCompletableDataFetcher(dataFetcher) - } - manuallyCompletableDataFetchers[level]?.add(manualDataFetcher) - return manualDataFetcher - } - - /** - * Complete all the [manuallyCompletableDataFetchers] - * - * @param level which level should complete dataFetchers - */ - fun completeDataFetchers(level: Level) { - manuallyCompletableDataFetchers[level]?.forEach(ManualDataFetcher::complete) - } - - /** - * Check if a given level is dispatched - * - * @param level which level check if its dispatched - */ - fun isLevelDispatched(level: Level): Boolean = when { - levelsState[level] == LevelState.DISPATCHED -> true - level.isFirst() -> dispatchedFetches[level] == expectedFetches[level] - else -> { - val previousLevel = level.previous() - isLevelDispatched(previousLevel) && - onFieldValueInfos[previousLevel] == expectedExecutionStrategies[previousLevel] && - dispatchedExecutionStrategies[level] == expectedExecutionStrategies[level] && - dispatchedFetches[level] == expectedFetches[level] - } - }.also { isLevelDispatched -> - if (isLevelDispatched) { - levelsState[level] = LevelState.DISPATCHED - } - } -} diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ExecutionLevelDispatchedState.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ExecutionLevelDispatchedState.kt deleted file mode 100644 index db93a0b514..0000000000 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ExecutionLevelDispatchedState.kt +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright 2024 Expedia, Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.graphql.dataloader.instrumentation.level.state - -import com.expediagroup.graphql.dataloader.instrumentation.extensions.getExpectedStrategyCalls -import com.expediagroup.graphql.dataloader.instrumentation.level.execution.OnLevelDispatchedCallback -import graphql.ExecutionInput -import graphql.ExecutionResult -import graphql.execution.ExecutionId -import graphql.execution.FieldValueInfo -import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext -import graphql.execution.instrumentation.InstrumentationContext -import graphql.execution.instrumentation.SimpleInstrumentationContext -import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters -import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters -import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters -import graphql.schema.DataFetcher -import java.util.concurrent.CompletableFuture -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicReference - -/** - * Orchestrate the [ExecutionBatchState] of all [ExecutionInput] sharing the same graphQLContext map, - * when a certain state is reached will invoke [OnLevelDispatchedCallback] - */ -class ExecutionLevelDispatchedState( - totalOperations: Int -) { - private val totalExecutions: AtomicReference = AtomicReference(totalOperations) - val executions = ConcurrentHashMap() - - /** - * Remove an [ExecutionBatchState] from the state in case operation does not qualify for execution, - * for example: - * parsing, validation, execution errors - * persisted query errors - */ - private fun removeExecution(executionId: ExecutionId) { - if (executions.containsKey(executionId)) { - executions.remove(executionId) - totalExecutions.set(totalExecutions.get() - 1) - } - } - - /** - * Initialize the [ExecutionBatchState] of this [ExecutionInput] - * - * @param parameters contains information of which [ExecutionInput] will start his execution - * @return a nullable [InstrumentationContext] - */ - fun beginExecution( - parameters: InstrumentationExecutionParameters - ): InstrumentationContext { - executions.computeIfAbsent(parameters.executionInput.executionId) { - ExecutionBatchState() - } - return object : SimpleInstrumentationContext() { - override fun onCompleted(result: ExecutionResult?, t: Throwable?) { - result?.let { - if (result.errors.size > 0) { - removeExecution(parameters.executionInput.executionId) - } - } - } - } - } - - /** - * When a specific [ExecutionInput] begins an executionStrategy, modify the state of his [ExecutionBatchState] - * - * @param parameters contains information of which [ExecutionInput] will start an ExecutionStrategy - * @param onLevelDispatched callback invoke when certain level of all operations is dispatched - */ - fun beginExecutionStrategy( - parameters: InstrumentationExecutionStrategyParameters, - onLevelDispatched: OnLevelDispatchedCallback - ): ExecutionStrategyInstrumentationContext { - val executionId = parameters.executionContext.executionInput.executionId - val level = Level(parameters.executionStrategyParameters.path.level + 1) - val fieldCount = parameters.executionStrategyParameters.fields.size() - - executions.computeIfPresent(executionId) { _, executionState -> - executionState.also { - it.initializeLevelStateIfNeeded(level) - it.increaseExpectedFetches(level, fieldCount) - it.increaseDispatchedExecutionStrategies(level) - } - } - - return object : ExecutionStrategyInstrumentationContext { - override fun onDispatched(result: CompletableFuture) { - } - - override fun onCompleted(result: ExecutionResult?, t: Throwable?) { - } - - override fun onFieldValuesInfo(fieldValueInfoList: List) { - val nextLevel = level.next() - - executions.computeIfPresent(executionId) { _, executionState -> - executionState.also { - it.increaseOnFieldValueInfos(level) - it.increaseExpectedExecutionStrategies( - nextLevel, - fieldValueInfoList.getExpectedStrategyCalls() - ) - } - } - - val allExecutionsDispatched = synchronized(executions) { allExecutionsDispatched(nextLevel) } - if (allExecutionsDispatched) { - onLevelDispatched(nextLevel, executions.keys().toList()) - executions.forEach { (_, executionState) -> executionState.completeDataFetchers(nextLevel) } - } - } - - override fun onFieldValuesException() { - executions.computeIfPresent(executionId) { _, executionState -> - executionState.also { - it.increaseOnFieldValueInfos(level) - } - } - } - } - } - - /** - * When a specific [ExecutionInput] begins an fieldFetch, modify the state of his [ExecutionBatchState] - * - * @param parameters contains information of which [ExecutionInput] will start an ExecutionStrategy - * @param onLevelDispatched invoke when certain level of all operations is dispatched - */ - fun beginFieldFetch( - parameters: InstrumentationFieldFetchParameters, - onLevelDispatched: OnLevelDispatchedCallback - ): InstrumentationContext { - val executionId = parameters.executionContext.executionInput.executionId - val path = parameters.executionStepInfo.path - val level = Level(path.level) - - return object : SimpleInstrumentationContext() { - override fun onDispatched(result: CompletableFuture) { - executions.computeIfPresent(executionId) { _, executionState -> - executionState.also { it.increaseDispatchedFetches(level) } - } - - val allExecutionsDispatched = synchronized(executions) { allExecutionsDispatched(level) } - if (allExecutionsDispatched) { - onLevelDispatched.invoke(level, executions.keys().toList()) - executions.forEach { (_, executionState) -> executionState.completeDataFetchers(level) } - } - } - } - } - - /** - * Modify runtime behaviour of ExecutionStrategy by instrumenting a data fetcher that can be - * manually completed, by default ExecutionStrategy will do a Depth First execution, by instrumenting - * the [dataFetcher] it will switch to Breath First execution to complete when a level of all operations sharing - * a graphQLContext was dispatched - * - * @param dataFetcher the original dataFetcher that will be instrumented - * @param parameters contains information of which [ExecutionInput] will use the [dataFetcher] - * @return [ManuallyCompletableDataFetcher] - */ - fun instrumentDataFetcher( - dataFetcher: DataFetcher<*>, - parameters: InstrumentationFieldFetchParameters - ): DataFetcher<*> { - var manuallyCompletableDataFetcher: DataFetcher<*> = dataFetcher - executions.computeIfPresent(parameters.executionContext.executionInput.executionId) { _, executionState -> - executionState.also { - manuallyCompletableDataFetcher = it.toManuallyCompletableDataFetcher( - Level(parameters.executionStepInfo.path.level), - dataFetcher - ) - } - } - return manuallyCompletableDataFetcher - } - - /** - * calculate if all executions sharing a graphQLContext was dispatched, by - * 1. Checking if all executions started. - * 3. check if all executions dispatched the provided [level]. - * - * @param level that execution state will be calculated - * @return Boolean for allExecutionsDispatched statement - */ - fun allExecutionsDispatched(level: Level): Boolean = synchronized(executions) { - val operationsToExecute = totalExecutions.get() - when { - executions.size < operationsToExecute -> false - else -> executions.all { (_, executionState) -> executionState.isLevelDispatched(level) } - } - } -} diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/Level.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/Level.kt deleted file mode 100644 index 5426703142..0000000000 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/Level.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2023 Expedia, Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.graphql.dataloader.instrumentation.level.state - -/** - * Represent a Level of a Document - */ -data class Level(private val number: Int) { - /** - * calculate [Level] that is after this [Level] - * @return next [Level] - */ - fun next(): Level = Level(number + 1) - - /** - * calculate [Level] that is before this [Level] - * @return previous [Level] - */ - fun previous(): Level = Level(number - 1) - - /** - * calculate if this [Level] is the first - * @return whether this is the first [Level] - */ - fun isFirst(): Boolean = number == 1 -} diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ManualDataFetcher.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ManualDataFetcher.kt deleted file mode 100644 index 070d8b5956..0000000000 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ManualDataFetcher.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2023 Expedia, Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.graphql.dataloader.instrumentation.level.state - -import graphql.schema.DataFetcher -import java.util.concurrent.CompletableFuture - -/** - * DataFetcher Decorator that allows manual completion of dataFetchers - */ -abstract class ManualDataFetcher : DataFetcher> { - val manualFuture: CompletableFuture = CompletableFuture() - var originalFuture: CompletableFuture? = null - var originalExpressionException: Exception? = null - - /** - * Manually complete the [manualFuture] by handling the [originalFuture] - */ - fun complete() { - when { - originalExpressionException != null -> manualFuture.completeExceptionally(originalExpressionException) - else -> originalFuture?.handle { result, exception -> - when { - exception != null -> manualFuture.completeExceptionally(exception) - else -> manualFuture.complete(result) - } - } - } - } -} diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ManuallyCompletableDataFetcher.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ManuallyCompletableDataFetcher.kt deleted file mode 100644 index d1db8b729b..0000000000 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ManuallyCompletableDataFetcher.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2023 Expedia, Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.graphql.dataloader.instrumentation.level.state - -import graphql.execution.Async -import graphql.schema.DataFetcher -import graphql.schema.DataFetchingEnvironment -import java.util.concurrent.CompletableFuture - -/** - * DataFetcher Decorator that stores the original dataFetcher result (it's always a completable future) - * it stores the [originalFuture] as property and returns an uncompleted [manualFuture] - * then at later point manually call [complete] to complete the [manualFuture] with the [originalFuture] result - * to let ExecutionStrategy handle all futures - * - * @param originalDataFetcher original dataFetcher to be decorated - */ -class ManuallyCompletableDataFetcher( - private val originalDataFetcher: DataFetcher<*> -) : ManualDataFetcher() { - /** - * when attempting to get the value from dataFetcher, execute the [originalDataFetcher] - * and store the resulting future [originalFuture] and a possible [originalExpressionException] if - * a synchronous exception was thrown during the execution - * - * @param environment dataFetchingEnvironment with information about the field - * @return an uncompleted manualFuture that can be completed at later time - */ - override fun get(environment: DataFetchingEnvironment): CompletableFuture { - try { - val fetchedValueRaw = originalDataFetcher.get(environment) - originalFuture = Async.toCompletableFuture(fetchedValueRaw) - } catch (e: Exception) { - originalExpressionException = e - } - return manualFuture - } -} diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ManuallyCompletableLightDataFetcher.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ManuallyCompletableLightDataFetcher.kt deleted file mode 100644 index 397b979377..0000000000 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ManuallyCompletableLightDataFetcher.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2023 Expedia, Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.graphql.dataloader.instrumentation.level.state - -import graphql.execution.Async -import graphql.schema.DataFetchingEnvironment -import graphql.schema.GraphQLFieldDefinition -import graphql.schema.LightDataFetcher -import java.util.concurrent.CompletableFuture -import java.util.function.Supplier - -/** - * LightDataFetcher Decorator that stores the original dataFetcher result (it's always a completable future) - * it stores the [originalFuture] as property and returns an uncompleted [manualFuture] - * then at later point manually call [complete] to complete the [manualFuture] with the [originalFuture] result - * to let ExecutionStrategy handle all futures - * - * @param originalDataFetcher original dataFetcher to be decorated - */ -class ManuallyCompletableLightDataFetcher( - private val originalDataFetcher: LightDataFetcher<*> -) : ManualDataFetcher(), LightDataFetcher> { - - override fun get(environment: DataFetchingEnvironment): CompletableFuture = - get(environment.fieldDefinition, environment.getSource()) { environment } - - /** - * when attempting to get the value from LightDataFetcher, execute the [originalDataFetcher] - * and store the resulting future [originalFuture] and a possible [originalExpressionException] if - * a synchronous exception was thrown during the execution - * - * @param fieldDefinition the graphql field definition - * @param sourceObject the source object to get a value from - * @param environmentSupplier a supplier of the [DataFetchingEnvironment] that creates it lazily - * @return an uncompleted manualFuture that can be completed at later time - */ - override fun get( - fieldDefinition: GraphQLFieldDefinition, - sourceObject: Any?, - environmentSupplier: Supplier - ): CompletableFuture { - try { - val fetchedValueRaw = originalDataFetcher.get( - fieldDefinition, - sourceObject, - environmentSupplier - ) - originalFuture = Async.toCompletableFuture(fetchedValueRaw) - } catch (e: Exception) { - originalExpressionException = e - } - return manualFuture - } -} diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/execution/AbstractSyncExecutionExhaustedInstrumentation.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/execution/AbstractSyncExecutionExhaustedInstrumentation.kt index 8d06df8f85..acb291e404 100644 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/execution/AbstractSyncExecutionExhaustedInstrumentation.kt +++ b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/execution/AbstractSyncExecutionExhaustedInstrumentation.kt @@ -23,7 +23,9 @@ import graphql.ExecutionResult import graphql.GraphQLContext import graphql.execution.ExecutionContext import graphql.execution.ExecutionId +import graphql.execution.instrumentation.ExecuteObjectInstrumentationContext import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext +import graphql.execution.instrumentation.FieldFetchingInstrumentationContext import graphql.execution.instrumentation.Instrumentation import graphql.execution.instrumentation.InstrumentationContext import graphql.execution.instrumentation.InstrumentationState @@ -64,18 +66,30 @@ abstract class AbstractSyncExecutionExhaustedInstrumentation : SimplePerformantI override fun beginExecutionStrategy( parameters: InstrumentationExecutionStrategyParameters, state: InstrumentationState? - ): ExecutionStrategyInstrumentationContext? = + ): ExecutionStrategyInstrumentationContext? { parameters.executionContext.takeUnless(ExecutionContext::isMutation) ?.graphQLContext?.get(SyncExecutionExhaustedState::class) - ?.beginExecutionStrategy(parameters) + ?.beginRecursiveExecution(parameters) + return null + } - override fun beginFieldFetch( + override fun beginExecuteObject( + parameters: InstrumentationExecutionStrategyParameters, + state: InstrumentationState? + ): ExecuteObjectInstrumentationContext? { + parameters.executionContext.takeUnless(ExecutionContext::isMutation) + ?.graphQLContext?.get(SyncExecutionExhaustedState::class) + ?.beginRecursiveExecution(parameters) + return null + } + + override fun beginFieldFetching( parameters: InstrumentationFieldFetchParameters, state: InstrumentationState? - ): InstrumentationContext? = + ): FieldFetchingInstrumentationContext? = parameters.executionContext.takeUnless(ExecutionContext::isMutation) ?.graphQLContext?.get(SyncExecutionExhaustedState::class) - ?.beginFieldFetch( + ?.beginFieldFetching( parameters, this.getOnSyncExecutionExhaustedCallback( SyncExecutionExhaustedInstrumentationParameters(parameters.executionContext) diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/state/ExecutionBatchState.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/state/ExecutionBatchState.kt index 7d5cae1986..677fff041d 100644 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/state/ExecutionBatchState.kt +++ b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/state/ExecutionBatchState.kt @@ -22,7 +22,6 @@ import graphql.language.Field import graphql.schema.DataFetcher import graphql.schema.GraphQLType import graphql.schema.GraphQLTypeUtil.isList -import java.util.concurrent.CompletableFuture import java.util.concurrent.ConcurrentHashMap /** @@ -143,13 +142,13 @@ class ExecutionBatchState { * @param field the [Field] that will transition to dispatched state. * @param fieldExecutionStrategyPath the [ResultPath] associated to the ExecutionStrategy that dispatched the [field]. * @param fieldGraphQLType the [GraphQLType] of the [field]. - * @param result the [DataFetcher] [CompletableFuture] result. + * @param result the [DataFetcher] polymorphic result, either a nullable materialized object or a CompletableFuture. */ fun fieldToDispatchedState( field: Field, fieldExecutionStrategyPath: ResultPath, fieldGraphQLType: GraphQLType, - result: CompletableFuture + result: Any? ) { val fieldExecutionStrategyPathString = fieldExecutionStrategyPath.toString() executionStrategiesState[fieldExecutionStrategyPathString]?.let { executionStrategyState -> diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/state/ExecutionStrategyState.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/state/ExecutionStrategyState.kt index 9b1d6b721e..d7cd9bc8aa 100644 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/state/ExecutionStrategyState.kt +++ b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/state/ExecutionStrategyState.kt @@ -108,20 +108,19 @@ class ExecutionStrategyState( * of the [Field] [DataFetcher] in order to calculate the [Field] [FieldFetchType]. * * @param graphQLType [GraphQLType] of the [Field] which [DataFetcher] was dispatched. - * @param result [CompletableFuture] result of the [DataFetcher.get] call. + * @param result the [DataFetcher] polymorphic result, either a nullable materialized object or a CompletableFuture. * @return this [FieldState]. */ fun toDispatchedState( graphQLType: GraphQLType, - result: CompletableFuture + result: Any? ): FieldState = this.also { this.fetchState = FieldFetchState.DISPATCHED this.graphQLType = graphQLType this.fetchType = when { - result.isDone -> FieldFetchType.SYNC - else -> FieldFetchType.ASYNC + result is CompletableFuture<*> -> FieldFetchType.ASYNC + else -> FieldFetchType.SYNC } - this@ExecutionStrategyState.dispatchedFields.updateAndGet { current -> current + 1 } } diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/state/SyncExecutionExhaustedState.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/state/SyncExecutionExhaustedState.kt index 14d1237585..17dc2b4875 100644 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/state/SyncExecutionExhaustedState.kt +++ b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/state/SyncExecutionExhaustedState.kt @@ -24,6 +24,7 @@ import graphql.GraphQLContext import graphql.execution.ExecutionId import graphql.execution.MergedField import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext +import graphql.execution.instrumentation.FieldFetchingInstrumentationContext import graphql.execution.instrumentation.InstrumentationContext import graphql.execution.instrumentation.SimpleInstrumentationContext import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters @@ -83,17 +84,16 @@ class SyncExecutionExhaustedState( } /** - * Add [ExecutionStrategyState] into operation [ExecutionBatchState] for the field that + * Add [ExecutionStrategyState] into operation [ExecutionBatchState] for the object that * just started an ExecutionStrategy * * @param parameters contains information of which [ExecutionInput] started an executionStrategy * @return a noop [ExecutionStrategyInstrumentationContext] */ - fun beginExecutionStrategy( + fun beginRecursiveExecution( parameters: InstrumentationExecutionStrategyParameters - ): ExecutionStrategyInstrumentationContext? { + ) { val executionId = parameters.executionContext.executionInput.executionId - executions.computeIfPresent(executionId) { _, executionState -> val executionStrategyParameters = parameters.executionStrategyParameters @@ -105,8 +105,6 @@ class SyncExecutionExhaustedState( executionState.addExecutionStrategyState(field, path, selectionFields, parentGraphQLType) executionState } - - return null } /** @@ -117,19 +115,19 @@ class SyncExecutionExhaustedState( * @return a [InstrumentationContext] object that will be called back when the [DataFetcher] * dispatches and completes */ - fun beginFieldFetch( + fun beginFieldFetching( parameters: InstrumentationFieldFetchParameters, onSyncExecutionExhausted: OnSyncExecutionExhaustedCallback - ): InstrumentationContext { + ): FieldFetchingInstrumentationContext { val executionId = parameters.executionContext.executionInput.executionId val field = parameters.executionStepInfo.field.singleField val fieldExecutionStrategyPath = parameters.executionStepInfo.path.parent val fieldGraphQLType = parameters.executionStepInfo.unwrappedNonNullType - return object : InstrumentationContext { - override fun onDispatched(result: CompletableFuture) { + return object : FieldFetchingInstrumentationContext { + override fun onFetchedValue(fetchedValue: Any?) { executions.computeIfPresent(executionId) { _, executionState -> - executionState.fieldToDispatchedState(field, fieldExecutionStrategyPath, fieldGraphQLType, result) + executionState.fieldToDispatchedState(field, fieldExecutionStrategyPath, fieldGraphQLType, fetchedValue) executionState } @@ -138,6 +136,7 @@ class SyncExecutionExhaustedState( onSyncExecutionExhausted(executions.keys().toList()) } } + override fun onCompleted(result: Any?, t: Throwable?) { executions.computeIfPresent(executionId) { _, executionState -> executionState.fieldToCompletedState(field, fieldExecutionStrategyPath, result) @@ -149,6 +148,9 @@ class SyncExecutionExhaustedState( onSyncExecutionExhausted(executions.keys().toList()) } } + + override fun onDispatched() { + } } } diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/fixture/AstronautGraphQL.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/fixture/AstronautGraphQL.kt index 4757c0dbf0..0d0ae4d06d 100644 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/fixture/AstronautGraphQL.kt +++ b/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/fixture/AstronautGraphQL.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Expedia, Inc + * Copyright 2024 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,6 @@ import com.expediagroup.graphql.dataloader.instrumentation.fixture.datafetcher.P import com.expediagroup.graphql.dataloader.instrumentation.fixture.datafetcher.PlanetServiceRequest import com.expediagroup.graphql.dataloader.instrumentation.fixture.domain.Mission import com.expediagroup.graphql.dataloader.instrumentation.fixture.domain.Nasa -import com.expediagroup.graphql.dataloader.instrumentation.level.state.ExecutionLevelDispatchedState import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.state.SyncExecutionExhaustedState import graphql.ExecutionInput import graphql.ExecutionResult @@ -217,10 +216,6 @@ object AstronautGraphQL { queries.size, kotlinDataLoaderRegistry ) - DataLoaderInstrumentationStrategy.LEVEL_DISPATCHED -> - ExecutionLevelDispatchedState::class to ExecutionLevelDispatchedState( - queries.size - ) } ) diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/fixture/DataLoaderInstrumentationStrategy.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/fixture/DataLoaderInstrumentationStrategy.kt index 36c85f7ebe..3bb4b43080 100644 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/fixture/DataLoaderInstrumentationStrategy.kt +++ b/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/fixture/DataLoaderInstrumentationStrategy.kt @@ -1,3 +1,3 @@ package com.expediagroup.graphql.dataloader.instrumentation.fixture -enum class DataLoaderInstrumentationStrategy { LEVEL_DISPATCHED, SYNC_EXHAUSTION } +enum class DataLoaderInstrumentationStrategy { SYNC_EXHAUSTION } diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/fixture/ProductGraphQL.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/fixture/ProductGraphQL.kt index 11149436b4..50aef25a12 100644 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/fixture/ProductGraphQL.kt +++ b/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/fixture/ProductGraphQL.kt @@ -24,7 +24,6 @@ import com.expediagroup.graphql.dataloader.instrumentation.fixture.datafetcher.P import com.expediagroup.graphql.dataloader.instrumentation.fixture.domain.Product import com.expediagroup.graphql.dataloader.instrumentation.fixture.domain.ProductDetails import com.expediagroup.graphql.dataloader.instrumentation.fixture.domain.ProductSummary -import com.expediagroup.graphql.dataloader.instrumentation.level.state.ExecutionLevelDispatchedState import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.state.SyncExecutionExhaustedState import graphql.ExecutionInput import graphql.ExecutionResult @@ -125,10 +124,6 @@ object ProductGraphQL { queries.size, kotlinDataLoaderRegistry ) - DataLoaderInstrumentationStrategy.LEVEL_DISPATCHED -> - ExecutionLevelDispatchedState::class to ExecutionLevelDispatchedState( - queries.size - ) } ) diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/DataLoaderLevelDispatchedInstrumentationTest.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/DataLoaderLevelDispatchedInstrumentationTest.kt deleted file mode 100644 index 9fde9a5082..0000000000 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/DataLoaderLevelDispatchedInstrumentationTest.kt +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright 2024 Expedia, Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.graphql.dataloader.instrumentation.level - -import com.expediagroup.graphql.dataloader.instrumentation.fixture.AstronautGraphQL -import com.expediagroup.graphql.dataloader.instrumentation.fixture.DataLoaderInstrumentationStrategy -import io.mockk.clearAllMocks -import io.mockk.verify -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import kotlin.test.assertEquals - -class DataLoaderLevelDispatchedInstrumentationTest { - private val dataLoaderLevelDispatchedInstrumentation = DataLoaderLevelDispatchedInstrumentation() - private val graphQL = AstronautGraphQL.builder - .instrumentation(dataLoaderLevelDispatchedInstrumentation) - // graphql java adds DataLoaderDispatcherInstrumentation by default - .doNotAddDefaultInstrumentations() - .build() - - @BeforeEach - fun clear() { - clearAllMocks() - } - - @Test - fun `Instrumentation should batch transactions on async top level fields`() { - val queries = listOf( - "{ astronaut(id: 1) { name } }", - "{ astronaut(id: 2) { id name } }", - "{ mission(id: 3) { id designation } }", - "{ mission(id: 4) { designation } }" - ) - - val (results, kotlinDataLoaderRegistry) = AstronautGraphQL.execute( - graphQL, - queries, - DataLoaderInstrumentationStrategy.LEVEL_DISPATCHED - ) - - assertEquals(4, results.size) - - val astronautStatistics = kotlinDataLoaderRegistry.dataLoadersMap["AstronautDataLoader"]?.statistics - val missionStatistics = kotlinDataLoaderRegistry.dataLoadersMap["MissionDataLoader"]?.statistics - - assertEquals(1, astronautStatistics?.batchInvokeCount) - assertEquals(2, astronautStatistics?.batchLoadCount) - - assertEquals(1, missionStatistics?.batchInvokeCount) - assertEquals(2, missionStatistics?.batchLoadCount) - - verify(exactly = 2 + ONE_LEVEL) { - kotlinDataLoaderRegistry.dispatchAll() - } - } - - @Test - fun `Instrumentation should batch transactions on sync top level fields`() { - val queries = listOf( - "{ nasa { astronaut(id: 1) { name } } }", - "{ nasa { astronaut(id: 2) { id name } } }", - "{ nasa { mission(id: 3) { designation } } }", - "{ nasa { mission(id: 4) { id designation } } }" - ) - - val (results, kotlinDataLoaderRegistry) = AstronautGraphQL.execute( - graphQL, - queries, - DataLoaderInstrumentationStrategy.LEVEL_DISPATCHED - ) - - assertEquals(4, results.size) - - val astronautStatistics = kotlinDataLoaderRegistry.dataLoadersMap["AstronautDataLoader"]?.statistics - val missionStatistics = kotlinDataLoaderRegistry.dataLoadersMap["MissionDataLoader"]?.statistics - - assertEquals(1, astronautStatistics?.batchInvokeCount) - assertEquals(2, astronautStatistics?.batchLoadCount) - - assertEquals(1, missionStatistics?.batchInvokeCount) - assertEquals(2, missionStatistics?.batchLoadCount) - - verify(exactly = 3 + ONE_LEVEL) { - kotlinDataLoaderRegistry.dispatchAll() - } - } - - @Test - fun `Instrumentation should batch by level even if different levels attempt to use same dataFetchers`() { - val queries = listOf( - // L2 astronaut - L3 missions - "{ nasa { astronaut(id: 1) { id name missions { designation } } } }", - // L1 astronaut - L2 missions - "{ astronaut(id: 2) { id name missions { designation } } }", - // L2 mission - "{ nasa { mission(id: 3) { designation } } }", - // L1 mission - "{ mission(id: 4) { designation } }" - ) - - val (results, kotlinDataLoaderRegistry) = AstronautGraphQL.execute( - graphQL, - queries, - DataLoaderInstrumentationStrategy.LEVEL_DISPATCHED - ) - - assertEquals(4, results.size) - - val astronautStatistics = kotlinDataLoaderRegistry.dataLoadersMap["AstronautDataLoader"]?.statistics - val missionStatistics = kotlinDataLoaderRegistry.dataLoadersMap["MissionDataLoader"]?.statistics - val missionsByAstronautStatistics = kotlinDataLoaderRegistry.dataLoadersMap["MissionsByAstronautDataLoader"]?.statistics - - // 1 for Level 1, 1 for Level 2 - assertEquals(2, astronautStatistics?.batchInvokeCount) - assertEquals(2, astronautStatistics?.batchLoadCount) - - // 1 for Level 1, 1 for Level 2 - assertEquals(2, missionStatistics?.batchInvokeCount) - assertEquals(2, missionStatistics?.batchLoadCount) - - // 1 for Level 2, 1 for Level 3 - assertEquals(2, missionsByAstronautStatistics?.batchInvokeCount) - assertEquals(2, missionsByAstronautStatistics?.batchLoadCount) - - verify(exactly = 4 + ONE_LEVEL) { - kotlinDataLoaderRegistry.dispatchAll() - } - } - - @Test - fun `Instrumentation should not apply to mutations`() { - val queries = listOf( - """mutation { createAstronaut(name: "spaceMan") { id name } }""" - ) - - val (results, kotlinDataLoaderRegistry) = AstronautGraphQL.execute( - graphQL, - queries, - DataLoaderInstrumentationStrategy.LEVEL_DISPATCHED - ) - - assertEquals(1, results.size) - verify(exactly = 0) { - kotlinDataLoaderRegistry.dispatchAll() - } - } - - @Test - fun `Instrumentation should not account for invalid operations`() { - val queries = listOf( - "invalid query{ astronaut(id: 1) {", - "{ astronaut(id: 2) { id name } }", - "{ mission(id: 3) { id designation } }", - "{ mission(id: 4) { designation } }" - ) - - val (results, kotlinDataLoaderRegistry) = AstronautGraphQL.execute( - graphQL, - queries, - DataLoaderInstrumentationStrategy.LEVEL_DISPATCHED - ) - - assertEquals(4, results.size) - - val astronautStatistics = kotlinDataLoaderRegistry.dataLoadersMap["AstronautDataLoader"]?.statistics - val missionStatistics = kotlinDataLoaderRegistry.dataLoadersMap["MissionDataLoader"]?.statistics - - assertEquals(1, astronautStatistics?.batchInvokeCount) - assertEquals(1, astronautStatistics?.batchLoadCount) - - assertEquals(1, missionStatistics?.batchInvokeCount) - assertEquals(2, missionStatistics?.batchLoadCount) - - verify(exactly = 2 + ONE_LEVEL) { - kotlinDataLoaderRegistry.dispatchAll() - } - } - - companion object { - private const val ONE_LEVEL = 1 - } -} diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/DataLoaderSyncExecutionExhaustedInstrumentationTest.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/DataLoaderSyncExecutionExhaustedInstrumentationTest.kt index 4147d49570..b6c4387f85 100644 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/DataLoaderSyncExecutionExhaustedInstrumentationTest.kt +++ b/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/DataLoaderSyncExecutionExhaustedInstrumentationTest.kt @@ -31,13 +31,13 @@ class DataLoaderSyncExecutionExhaustedInstrumentationTest { private val graphQL = AstronautGraphQL.builder .instrumentation(dataLoaderSyncExecutionExhaustedInstrumentation) // graphql java adds DataLoaderDispatcherInstrumentation by default - .doNotAddDefaultInstrumentations() + .doNotAutomaticallyDispatchDataLoader() .build() private val productGraphQL = ProductGraphQL.builder .instrumentation(DataLoaderSyncExecutionExhaustedInstrumentation()) // graphql java adds DataLoaderDispatcherInstrumentation by default - .doNotAddDefaultInstrumentations() + .doNotAutomaticallyDispatchDataLoader() .build() @BeforeEach @@ -569,7 +569,7 @@ class DataLoaderSyncExecutionExhaustedInstrumentationTest { val (results, dataLoaderSyncExecutionExhaustedInstrumentation) = AstronautGraphQL.execute( graphQL, queries, - DataLoaderInstrumentationStrategy.LEVEL_DISPATCHED + DataLoaderInstrumentationStrategy.SYNC_EXHAUSTION ) assertEquals(1, results.size) diff --git a/generator/graphql-kotlin-federation/build.gradle.kts b/generator/graphql-kotlin-federation/build.gradle.kts index 41fcab7d19..9cbe08ccd0 100644 --- a/generator/graphql-kotlin-federation/build.gradle.kts +++ b/generator/graphql-kotlin-federation/build.gradle.kts @@ -6,7 +6,10 @@ plugins { dependencies { api(projects.graphqlKotlinSchemaGenerator) - api(libs.federation) + api(libs.federation) { + exclude(group = "com.graphql-java", module = "graphql-java") + } + api(libs.graphql.java) testImplementation(libs.reactor.core) testImplementation(libs.reactor.extensions) testImplementation(libs.junit.params) diff --git a/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/data/TestResolvers.kt b/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/data/TestResolvers.kt index df782a0043..9689b08533 100644 --- a/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/data/TestResolvers.kt +++ b/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/data/TestResolvers.kt @@ -27,7 +27,7 @@ import java.util.concurrent.CompletableFuture internal class BookResolver : FederatedTypeSuspendResolver { override val typeName: String = "Book" - override suspend fun resolve(environment: DataFetchingEnvironment, representation: Map): Book? { + override suspend fun resolve(environment: DataFetchingEnvironment, representation: Map): Book { val book = Book(representation["id"].toString()) representation["weight"]?.toString()?.toDoubleOrNull()?.let { book.weight = it @@ -39,7 +39,7 @@ internal class BookResolver : FederatedTypeSuspendResolver { internal class UserResolver : FederatedTypeSuspendResolver { override val typeName: String = "User" - override suspend fun resolve(environment: DataFetchingEnvironment, representation: Map): User? { + override suspend fun resolve(environment: DataFetchingEnvironment, representation: Map): User { val id = representation["userId"].toString().toInt() val name = representation["name"].toString() return User(id, name) diff --git a/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/execution/ServiceQueryResolverTest.kt b/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/execution/ServiceQueryResolverTest.kt index 94c67493c9..522e0e68d4 100644 --- a/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/execution/ServiceQueryResolverTest.kt +++ b/generator/graphql-kotlin-federation/src/test/kotlin/com/expediagroup/graphql/generator/federation/execution/ServiceQueryResolverTest.kt @@ -71,7 +71,7 @@ type Query @extends type Review { body: String! - content: String @deprecated(reason : "no longer supported, replace with use Review.body instead") + content: String customScalar: CustomScalar! id: String! } @@ -173,7 +173,7 @@ type Query { type Review { body: String! @custom - content: String @deprecated(reason : "no longer supported, replace with use Review.body instead") + content: String customScalar: CustomScalar! id: String! } diff --git a/generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/execution/FlowSubscriptionExecutionStrategy.kt b/generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/execution/FlowSubscriptionExecutionStrategy.kt index 28ef374b61..61146a197f 100644 --- a/generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/execution/FlowSubscriptionExecutionStrategy.kt +++ b/generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/execution/FlowSubscriptionExecutionStrategy.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Expedia, Inc + * Copyright 2024 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import graphql.execution.ExecutionContext import graphql.execution.ExecutionStepInfo import graphql.execution.ExecutionStrategy import graphql.execution.ExecutionStrategyParameters +import graphql.execution.FetchedValue import graphql.execution.SimpleDataFetcherExceptionHandler import graphql.execution.SubscriptionExecutionStrategy import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext @@ -84,7 +85,7 @@ class FlowSubscriptionExecutionStrategy(dfe: DataFetcherExceptionHandler) : Exec } // dispatched the subscription query - executionStrategyCtx.onDispatched(overallResult) + executionStrategyCtx.onDispatched() overallResult.whenComplete(executionStrategyCtx::onCompleted) return overallResult @@ -103,13 +104,20 @@ class FlowSubscriptionExecutionStrategy(dfe: DataFetcherExceptionHandler) : Exec Let {fieldStream} be the result of running {ResolveFieldEventStream(subscriptionType, initialValue, rootField, argumentValues)}. Return {fieldStream}. */ + @Suppress("UNCHECKED_CAST") private fun createSourceEventStream( executionContext: ExecutionContext, parameters: ExecutionStrategyParameters ): CompletableFuture?> { val newParameters = firstFieldOfSubscriptionSelection(parameters) - val fieldFetched = fetchField(executionContext, newParameters) + val fieldFetched: CompletableFuture = fetchField(executionContext, newParameters).let { fetchedValue -> + if (fetchedValue is CompletableFuture<*>) { + fetchedValue as CompletableFuture + } else { + CompletableFuture.completedFuture(fetchedValue as FetchedValue) + } + } return fieldFetched.thenApply { fetchedValue -> val flow = when (val publisherOrFlow: Any? = fetchedValue.fetchedValue) { is Publisher<*> -> publisherOrFlow.asFlow() @@ -163,12 +171,12 @@ class FlowSubscriptionExecutionStrategy(dfe: DataFetcherExceptionHandler) : Exec .thenApply { executionResult -> wrapWithRootFieldName(newParameters, executionResult) } // dispatch instrumentation so they can know about each subscription event - subscribedFieldCtx.onDispatched(overallResult) + subscribedFieldCtx.onDispatched() overallResult.whenComplete(subscribedFieldCtx::onCompleted) // allow them to instrument each ER should they want to val i13nExecutionParameters = InstrumentationExecutionParameters( - executionContext.executionInput, executionContext.graphQLSchema, executionContext.instrumentationState + executionContext.executionInput, executionContext.graphQLSchema ) return overallResult.thenCompose { executionResult -> diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5da1198082..2835826d59 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,9 +1,9 @@ [versions] android-plugin = "8.1.1" classgraph = "4.8.170" -dataloader = "3.2.2" +dataloader = "3.3.0" federation = "4.4.1" -graphql-java = "21.5" +graphql-java = "0.0.0-2024-04-23-expedia-branch-1" graalvm = "0.10.1" jackson = "2.15.4" # kotlin version has to match the compile-testing compiler version diff --git a/plugins/client/graphql-kotlin-client-generator/src/main/kotlin/com/expediagroup/graphql/plugin/client/generator/GraphQLClientGenerator.kt b/plugins/client/graphql-kotlin-client-generator/src/main/kotlin/com/expediagroup/graphql/plugin/client/generator/GraphQLClientGenerator.kt index e4b02c55ec..50ac01f991 100644 --- a/plugins/client/graphql-kotlin-client-generator/src/main/kotlin/com/expediagroup/graphql/plugin/client/generator/GraphQLClientGenerator.kt +++ b/plugins/client/graphql-kotlin-client-generator/src/main/kotlin/com/expediagroup/graphql/plugin/client/generator/GraphQLClientGenerator.kt @@ -33,6 +33,7 @@ import com.squareup.kotlinpoet.TypeSpec import graphql.language.ObjectTypeDefinition import graphql.language.OperationDefinition import graphql.parser.Parser +import graphql.parser.ParserEnvironment import graphql.parser.ParserOptions import graphql.schema.idl.SchemaParser import graphql.schema.idl.TypeDefinitionRegistry @@ -94,9 +95,14 @@ class GraphQLClientGenerator( */ internal fun generate(queryFile: File): List { val queryConst = queryFile.readText().trim() - val queryDocument = documentParser.parseDocument(queryConst, parserOptions) + val queryDocument = documentParser.parseDocument( + ParserEnvironment.newParserEnvironment() + .document(queryConst) + .parserOptions(parserOptions) + .build() + ) - val operationDefinitions = queryDocument.definitions.filterIsInstance(OperationDefinition::class.java) + val operationDefinitions = queryDocument.definitions.filterIsInstance() if (operationDefinitions.size > 1) { throw MultipleOperationsInFileException(queryFile) } diff --git a/servers/graphql-kotlin-ktor-server/src/main/kotlin/com/expediagroup/graphql/server/ktor/GraphQL.kt b/servers/graphql-kotlin-ktor-server/src/main/kotlin/com/expediagroup/graphql/server/ktor/GraphQL.kt index bc6693f7ac..93e324ab58 100644 --- a/servers/graphql-kotlin-ktor-server/src/main/kotlin/com/expediagroup/graphql/server/ktor/GraphQL.kt +++ b/servers/graphql-kotlin-ktor-server/src/main/kotlin/com/expediagroup/graphql/server/ktor/GraphQL.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 Expedia, Inc + * Copyright 2024 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ package com.expediagroup.graphql.server.ktor import com.apollographql.federation.graphqljava.tracing.FederatedTracingInstrumentation import com.expediagroup.graphql.apq.provider.AutomaticPersistedQueriesProvider -import com.expediagroup.graphql.dataloader.instrumentation.level.DataLoaderLevelDispatchedInstrumentation import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.DataLoaderSyncExecutionExhaustedInstrumentation import com.expediagroup.graphql.generator.ClasspathTypeResolver import com.expediagroup.graphql.generator.SchemaGenerator @@ -135,10 +134,9 @@ class GraphQL(config: GraphQLConfiguration) { val instrumentations = mutableListOf() if (config.engine.batching.enabled) { - builder.doNotAddDefaultInstrumentations() + builder.doNotAutomaticallyDispatchDataLoader() instrumentations.add( when (config.engine.batching.strategy) { - GraphQLConfiguration.BatchingStrategy.LEVEL_DISPATCHED -> DataLoaderLevelDispatchedInstrumentation() GraphQLConfiguration.BatchingStrategy.SYNC_EXHAUSTION -> DataLoaderSyncExecutionExhaustedInstrumentation() } ) diff --git a/servers/graphql-kotlin-ktor-server/src/main/kotlin/com/expediagroup/graphql/server/ktor/GraphQLConfiguration.kt b/servers/graphql-kotlin-ktor-server/src/main/kotlin/com/expediagroup/graphql/server/ktor/GraphQLConfiguration.kt index 36bb50954f..077dab9079 100644 --- a/servers/graphql-kotlin-ktor-server/src/main/kotlin/com/expediagroup/graphql/server/ktor/GraphQLConfiguration.kt +++ b/servers/graphql-kotlin-ktor-server/src/main/kotlin/com/expediagroup/graphql/server/ktor/GraphQLConfiguration.kt @@ -235,7 +235,7 @@ class GraphQLConfiguration(config: ApplicationConfig) { /** * Approaches for batching transactions of a set of GraphQL Operations. */ - enum class BatchingStrategy { LEVEL_DISPATCHED, SYNC_EXHAUSTION } + enum class BatchingStrategy { SYNC_EXHAUSTION } /** * Batching configuration properties. @@ -288,4 +288,4 @@ class GraphQLConfiguration(config: ApplicationConfig) { } private fun String?.toBatchingStrategy(): GraphQLConfiguration.BatchingStrategy = - GraphQLConfiguration.BatchingStrategy.values().firstOrNull { strategy -> strategy.name == this } ?: GraphQLConfiguration.BatchingStrategy.LEVEL_DISPATCHED + GraphQLConfiguration.BatchingStrategy.values().firstOrNull { strategy -> strategy.name == this } ?: GraphQLConfiguration.BatchingStrategy.SYNC_EXHAUSTION diff --git a/servers/graphql-kotlin-server/src/main/kotlin/com/expediagroup/graphql/server/execution/GraphQLRequestHandler.kt b/servers/graphql-kotlin-server/src/main/kotlin/com/expediagroup/graphql/server/execution/GraphQLRequestHandler.kt index 6808d1021e..d0a2e7209d 100644 --- a/servers/graphql-kotlin-server/src/main/kotlin/com/expediagroup/graphql/server/execution/GraphQLRequestHandler.kt +++ b/servers/graphql-kotlin-server/src/main/kotlin/com/expediagroup/graphql/server/execution/GraphQLRequestHandler.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 Expedia, Inc + * Copyright 2024 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,6 @@ package com.expediagroup.graphql.server.execution import com.expediagroup.graphql.dataloader.KotlinDataLoaderRegistry import com.expediagroup.graphql.dataloader.KotlinDataLoaderRegistryFactory -import com.expediagroup.graphql.dataloader.instrumentation.level.DataLoaderLevelDispatchedInstrumentation -import com.expediagroup.graphql.dataloader.instrumentation.level.state.ExecutionLevelDispatchedState import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.DataLoaderSyncExecutionExhaustedInstrumentation import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.state.SyncExecutionExhaustedState import com.expediagroup.graphql.generator.extensions.plus @@ -140,22 +138,13 @@ open class GraphQLRequestHandler( batchSize: Int, dataLoaderRegistry: KotlinDataLoaderRegistry? ): Map<*, Any> { - if (dataLoaderRegistry == null) { - return emptyMap() - } - - val batchContext = when (batchDataLoaderInstrumentationType) { - DataLoaderLevelDispatchedInstrumentation::class.java -> mapOf( - ExecutionLevelDispatchedState::class to ExecutionLevelDispatchedState(batchSize) - ) - + dataLoaderRegistry ?: return emptyMap() + return when (batchDataLoaderInstrumentationType) { DataLoaderSyncExecutionExhaustedInstrumentation::class.java -> mapOf( SyncExecutionExhaustedState::class to SyncExecutionExhaustedState(batchSize, dataLoaderRegistry) ) - else -> emptyMap() } - return batchContext } /** diff --git a/servers/graphql-kotlin-server/src/main/kotlin/com/expediagroup/graphql/server/extensions/instrumentationExtensions.kt b/servers/graphql-kotlin-server/src/main/kotlin/com/expediagroup/graphql/server/extensions/instrumentationExtensions.kt index eacc486174..087554227b 100644 --- a/servers/graphql-kotlin-server/src/main/kotlin/com/expediagroup/graphql/server/extensions/instrumentationExtensions.kt +++ b/servers/graphql-kotlin-server/src/main/kotlin/com/expediagroup/graphql/server/extensions/instrumentationExtensions.kt @@ -1,9 +1,7 @@ package com.expediagroup.graphql.server.extensions -import com.expediagroup.graphql.dataloader.instrumentation.level.DataLoaderLevelDispatchedInstrumentation import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.DataLoaderSyncExecutionExhaustedInstrumentation import graphql.execution.instrumentation.Instrumentation internal fun Instrumentation.isBatchDataLoaderInstrumentation(): Boolean = - javaClass == DataLoaderLevelDispatchedInstrumentation::class.java || - javaClass == DataLoaderSyncExecutionExhaustedInstrumentation::class.java + javaClass == DataLoaderSyncExecutionExhaustedInstrumentation::class.java diff --git a/servers/graphql-kotlin-server/src/test/kotlin/com/expediagroup/graphql/server/execution/GraphQLRequestHandlerTest.kt b/servers/graphql-kotlin-server/src/test/kotlin/com/expediagroup/graphql/server/execution/GraphQLRequestHandlerTest.kt index b666b8ba33..b2813c32b6 100644 --- a/servers/graphql-kotlin-server/src/test/kotlin/com/expediagroup/graphql/server/execution/GraphQLRequestHandlerTest.kt +++ b/servers/graphql-kotlin-server/src/test/kotlin/com/expediagroup/graphql/server/execution/GraphQLRequestHandlerTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 Expedia, Inc + * Copyright 2024 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ package com.expediagroup.graphql.server.execution import com.expediagroup.graphql.dataloader.KotlinDataLoader import com.expediagroup.graphql.dataloader.KotlinDataLoaderRegistryFactory -import com.expediagroup.graphql.dataloader.instrumentation.level.DataLoaderLevelDispatchedInstrumentation import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.DataLoaderSyncExecutionExhaustedInstrumentation import com.expediagroup.graphql.generator.SchemaGeneratorConfig import com.expediagroup.graphql.generator.TopLevelObject @@ -71,7 +70,7 @@ class GraphQLRequestHandlerTest { private fun getBatchingRequestHandler(instrumentation: Instrumentation): GraphQLRequestHandler = GraphQLRequestHandler( - GraphQL.newGraphQL(testSchema).doNotAddDefaultInstrumentations().instrumentation(instrumentation).build(), + GraphQL.newGraphQL(testSchema).doNotAutomaticallyDispatchDataLoader().instrumentation(instrumentation).build(), KotlinDataLoaderRegistryFactory( object : KotlinDataLoader { override val dataLoaderName: String = "UserDataLoader" @@ -303,7 +302,7 @@ class GraphQLRequestHandlerTest { ) val batchingRequestHandler = getBatchingRequestHandler( ChainedInstrumentation( - DataLoaderLevelDispatchedInstrumentation() + DataLoaderSyncExecutionExhaustedInstrumentation() ) ) batchingRequestHandler.executeRequest(request) as GraphQLBatchResponse @@ -333,7 +332,7 @@ class GraphQLRequestHandlerTest { val response = runBlocking { val context = emptyMap().toGraphQLContext() val request = GraphQLRequest( - query = "subscription { users(name: \"Jane Doe\") { name } }", + query = """subscription { users(name: "Jane Doe") { name } }""", ) graphQLRequestHandler.executeSubscription(request, context).first() } diff --git a/servers/graphql-kotlin-server/src/test/kotlin/com/expediagroup/graphql/server/execution/subscription/GraphQLWebSocketServerTest.kt b/servers/graphql-kotlin-server/src/test/kotlin/com/expediagroup/graphql/server/execution/subscription/GraphQLWebSocketServerTest.kt index 93beba186a..ac4159c879 100644 --- a/servers/graphql-kotlin-server/src/test/kotlin/com/expediagroup/graphql/server/execution/subscription/GraphQLWebSocketServerTest.kt +++ b/servers/graphql-kotlin-server/src/test/kotlin/com/expediagroup/graphql/server/execution/subscription/GraphQLWebSocketServerTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 Expedia, Inc + * Copyright 2024 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,7 +39,6 @@ import graphql.GraphQL import io.mockk.coEvery import io.mockk.coVerify import io.mockk.spyk -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay @@ -55,7 +54,6 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue -@OptIn(ExperimentalCoroutinesApi::class) class GraphQLWebSocketServerTest { private val mapper = jacksonObjectMapper() diff --git a/servers/graphql-kotlin-spring-server/src/main/kotlin/com/expediagroup/graphql/server/spring/GraphQLConfigurationProperties.kt b/servers/graphql-kotlin-spring-server/src/main/kotlin/com/expediagroup/graphql/server/spring/GraphQLConfigurationProperties.kt index 7fbbc90fdf..91d467de2c 100644 --- a/servers/graphql-kotlin-spring-server/src/main/kotlin/com/expediagroup/graphql/server/spring/GraphQLConfigurationProperties.kt +++ b/servers/graphql-kotlin-spring-server/src/main/kotlin/com/expediagroup/graphql/server/spring/GraphQLConfigurationProperties.kt @@ -143,7 +143,7 @@ data class GraphQLConfigurationProperties( /** Boolean flag to enable or disable batching for a set of GraphQL Operations. */ val enabled: Boolean = false, /** configure the [BatchingStrategy] that will be used when batching is enabled for a set of GraphQL Operations. */ - val strategy: BatchingStrategy = BatchingStrategy.LEVEL_DISPATCHED + val strategy: BatchingStrategy = BatchingStrategy.SYNC_EXHAUSTION ) data class AutomaticPersistedQueriesConfigurationProperties( @@ -164,6 +164,5 @@ enum class SubscriptionProtocol { * Approaches for batching transactions of a set of GraphQL Operations. */ enum class BatchingStrategy { - LEVEL_DISPATCHED, SYNC_EXHAUSTION } diff --git a/servers/graphql-kotlin-spring-server/src/main/kotlin/com/expediagroup/graphql/server/spring/GraphQLSchemaConfiguration.kt b/servers/graphql-kotlin-spring-server/src/main/kotlin/com/expediagroup/graphql/server/spring/GraphQLSchemaConfiguration.kt index d35600f702..b7914d7928 100644 --- a/servers/graphql-kotlin-spring-server/src/main/kotlin/com/expediagroup/graphql/server/spring/GraphQLSchemaConfiguration.kt +++ b/servers/graphql-kotlin-spring-server/src/main/kotlin/com/expediagroup/graphql/server/spring/GraphQLSchemaConfiguration.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 Expedia, Inc + * Copyright 2024 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ import com.expediagroup.graphql.apq.provider.AutomaticPersistedQueriesProvider import com.expediagroup.graphql.generator.execution.FlowSubscriptionExecutionStrategy import com.expediagroup.graphql.generator.scalars.IDValueUnboxer import com.expediagroup.graphql.dataloader.KotlinDataLoaderRegistryFactory -import com.expediagroup.graphql.dataloader.instrumentation.level.DataLoaderLevelDispatchedInstrumentation import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.DataLoaderSyncExecutionExhaustedInstrumentation import com.expediagroup.graphql.server.execution.GraphQLRequestHandler import com.expediagroup.graphql.server.spring.execution.DefaultSpringGraphQLContextFactory @@ -88,10 +87,9 @@ class GraphQLSchemaConfiguration { val instrumentations = mutableListOf() if (config.batching.enabled) { - builder.doNotAddDefaultInstrumentations() + builder.doNotAutomaticallyDispatchDataLoader() instrumentations.add( when (config.batching.strategy) { - BatchingStrategy.LEVEL_DISPATCHED -> DataLoaderLevelDispatchedInstrumentation() BatchingStrategy.SYNC_EXHAUSTION -> DataLoaderSyncExecutionExhaustedInstrumentation() } ) diff --git a/servers/graphql-kotlin-spring-server/src/test/kotlin/com/expediagroup/graphql/server/spring/instrumentation/InstrumentationIT.kt b/servers/graphql-kotlin-spring-server/src/test/kotlin/com/expediagroup/graphql/server/spring/instrumentation/InstrumentationIT.kt index 60267d3a90..0fbb71cc12 100644 --- a/servers/graphql-kotlin-spring-server/src/test/kotlin/com/expediagroup/graphql/server/spring/instrumentation/InstrumentationIT.kt +++ b/servers/graphql-kotlin-spring-server/src/test/kotlin/com/expediagroup/graphql/server/spring/instrumentation/InstrumentationIT.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 Expedia, Inc + * Copyright 2024 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,8 @@ import com.expediagroup.graphql.server.types.GraphQLRequest import graphql.ExecutionResult import graphql.ExecutionResultImpl import graphql.execution.instrumentation.Instrumentation -import graphql.execution.instrumentation.SimpleInstrumentation +import graphql.execution.instrumentation.InstrumentationState +import graphql.execution.instrumentation.SimplePerformantInstrumentation import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired @@ -59,8 +60,8 @@ class InstrumentationIT(@Autowired private val testClient: WebTestClient) { fun helloWorld(name: String) = "Hello $name!" } - class OrderedInstrumentation(private val instrumentationOrder: Int, private val counter: AtomicInteger) : SimpleInstrumentation(), Ordered { - override fun instrumentExecutionResult(executionResult: ExecutionResult, parameters: InstrumentationExecutionParameters): CompletableFuture { + class OrderedInstrumentation(private val instrumentationOrder: Int, private val counter: AtomicInteger) : SimplePerformantInstrumentation(), Ordered { + override fun instrumentExecutionResult(executionResult: ExecutionResult, parameters: InstrumentationExecutionParameters, state: InstrumentationState?): CompletableFuture { val currentExt: MutableMap = executionResult.extensions?.toMutableMap() ?: mutableMapOf() currentExt[instrumentationOrder] = counter.getAndIncrement() @@ -76,7 +77,7 @@ class InstrumentationIT(@Autowired private val testClient: WebTestClient) { .uri("/graphql") .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) - .bodyValue(GraphQLRequest("query { helloWorld(name: \"World\") }")) + .bodyValue(GraphQLRequest("""query { helloWorld(name: "World") }""")) .exchange() .expectBody() .jsonPath("$.data.helloWorld").isEqualTo("Hello World!") diff --git a/website/docs/assets/data-loader-level-dispatched-instrumentation.png b/website/docs/assets/data-loader-level-dispatched-instrumentation.png deleted file mode 100644 index a83d8150da450d110bd1989cde71059f0d0f8781..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 139009 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 z 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®! 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 azQLKXY^AAeX_&qP{uE?Xr0XP2L;fGQ*%pxi diff --git a/website/docs/server/data-loader/data-loader-instrumentation.mdx b/website/docs/server/data-loader/data-loader-instrumentation.mdx index 0aca14ec53..6568aa8a8b 100644 --- a/website/docs/server/data-loader/data-loader-instrumentation.mdx +++ b/website/docs/server/data-loader/data-loader-instrumentation.mdx @@ -18,100 +18,7 @@ allowing batching and deduplication of transactions across those multiple GraphQ By default, each GraphQL operation is processed independently of each other. Multiple operations can be processed together as if they were single GraphQL request if they are part of the same batch request. -The `graphql-kotlin-dataloader-instrumentation` module contains 2 custom `DataLoader` instrumentations. - -## Dispatching by level - -The `DataLoaderLevelDispatchedInstrumentation` tracks the state of all `ExecutionInputs` across operations. When a certain -field dispatches, it will check if all fields across all operations for a particular level were dispatched and if the condition is met, -it will dispatch all the data loaders. - -### Example - -You can find additional examples in our [unit tests](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/DataLoaderLevelDispatchedInstrumentationTest.kt). - - - - - -```graphql -query Q1 { - astronaut(id: 1) { # async - id - name - missions { # async - id - designation - } - } -} - -query Q2 { - astronaut(id: 2) { # async - id - name - missions { # async - id - designation - } - } -} -``` - - - - -![Image of data loader level dispatched instrumentation](../../assets/data-loader-level-dispatched-instrumentation.png) - -* The `astronaut` `DataFetcher` uses a `AstronautDataLoader` which will be dispatched when **Level 1** of those 2 operations -is dispatched, causing the `AstronautDataLoader` to load 2 astronauts. -* The `missions` `DataFetcher` uses a `MissionsByAstronautDataLoader` which will be dispatched when **Level 2** of those 2 operations -is dispatched, causing the `MissionsByAstronautDataLoader` to load 2 lists of missions by astronaut. - - - - -### Usage - -In order to enable batching by level, you need to configure your GraphQL instance with the `DataLoaderLevelDispatchedInstrumentation`. - -```kotlin -val graphQL = GraphQL.Builder() - .doNotAddDefaultInstrumentations() - .instrumentation(DataLoaderLevelDispatchedInstrumentation()) - // configure schema, type wiring, etc. - .build() -``` - -This data loader instrumentation relies on a global state object that needs be stored in the GraphQLContext map - -```kotlin -val graphQLContext = mapOf( - ExecutionLevelDispatchedState::class to ExecutionLevelDispatchedState(queries.size) -) -``` - -:::info -`graphql-kotlin-spring-server` provides convenient integration of batch loader functionality through simple configuration. -Batching by level can be enabled by configuring following properties: -```yaml -graphql: - batching: - enabled: true - strategy: LEVEL_DISPATCHED -``` -::: - -### Limitations - -This instrumentation is a good option if your **GraphQLServer** will receive a batched request with operations of the same type, -in those cases batching by level is enough, however, this solution is far from being the most optimal as we don't necessarily want to dispatch by level. +The `graphql-kotlin-dataloader-instrumentation` module contains 1 custom `DataLoader` instrumentation. ## Dispatching by synchronous execution exhaustion diff --git a/website/docs/server/spring-server/spring-properties.md b/website/docs/server/spring-server/spring-properties.md index 9ac6e3bec0..717e9f3ffa 100644 --- a/website/docs/server/spring-server/spring-properties.md +++ b/website/docs/server/spring-server/spring-properties.md @@ -31,4 +31,4 @@ details on the supported configuration properties. | graphql.subscriptions.keepAliveInterval | **Deprecated**. Keep the websocket alive and send a message to the client every interval in ms. Defaults to not sending messages | null | | graphql.subscriptions.protocol | WebSocket based subscription protocol. Supported protocols: APOLLO_SUBSCRIPTIONS_WS / GRAPHQL_WS | GRAPHQL_WS | | graphql.batching.enabled | Boolean flag indicating whether to enable custom dataloader instrumentations for 1 or more GraphQL Operations | false | -| graphql.batching.strategy | Configure which custom dataloader instrumentation will be used (LEVEL_DISPATCHED or SYNC_EXHAUSTION) | LEVEL_DISPATCHED | +| graphql.batching.strategy | Configure which custom dataloader instrumentation will be used | SYNC_EXHAUSTION |