Skip to content

Commit 6506e91

Browse files
samuelAndalonSamuel Vazquez
and
Samuel Vazquez
authored
feat(7.x.x): SingletonPropertyDataFetcher, avoid allocating a PropertyDataFetcher per property per graphql operation (#2081)
### 📝 Description cherry-pick #2079 --------- Co-authored-by: Samuel Vazquez <samvazquez@expediagroup.com>
1 parent 4596b6d commit 6506e91

File tree

7 files changed

+186
-78
lines changed

7 files changed

+186
-78
lines changed

generator/graphql-kotlin-schema-generator/build.gradle.kts

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ tasks {
2525
limit {
2626
counter = "BRANCH"
2727
value = "COVEREDRATIO"
28-
minimum = "0.92".toBigDecimal()
28+
minimum = "0.91".toBigDecimal()
2929
}
3030
}
3131
}

generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/execution/KotlinDataFetcherFactoryProvider.kt

+9
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,12 @@ open class SimpleKotlinDataFetcherFactoryProvider : KotlinDataFetcherFactoryProv
6464
PropertyDataFetcher(kProperty.getter)
6565
}
6666
}
67+
68+
/**
69+
* [SimpleSingletonKotlinDataFetcherFactoryProvider] is a specialization of [SimpleKotlinDataFetcherFactoryProvider] that will provide a
70+
* a [SingletonPropertyDataFetcher] that should be used to target property resolutions without allocating a DataFetcher per property
71+
*/
72+
open class SimpleSingletonKotlinDataFetcherFactoryProvider : SimpleKotlinDataFetcherFactoryProvider() {
73+
override fun propertyDataFetcherFactory(kClass: KClass<*>, kProperty: KProperty<*>): DataFetcherFactory<Any?> =
74+
SingletonPropertyDataFetcher.getFactoryAndRegister(kClass, kProperty)
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.expediagroup.graphql.generator.execution
2+
3+
import graphql.schema.DataFetcher
4+
import graphql.schema.DataFetcherFactory
5+
import graphql.schema.DataFetchingEnvironment
6+
import graphql.schema.GraphQLFieldDefinition
7+
import graphql.schema.LightDataFetcher
8+
import java.util.concurrent.ConcurrentHashMap
9+
import java.util.function.Supplier
10+
import kotlin.reflect.KClass
11+
import kotlin.reflect.KProperty
12+
13+
/**
14+
* Singleton Property [DataFetcher] that stores references to underlying properties getters.
15+
*/
16+
internal object SingletonPropertyDataFetcher : LightDataFetcher<Any?> {
17+
18+
private val factory: DataFetcherFactory<Any?> = DataFetcherFactory<Any?> { SingletonPropertyDataFetcher }
19+
20+
private val getters: ConcurrentHashMap<String, KProperty.Getter<*>> = ConcurrentHashMap()
21+
22+
fun getFactoryAndRegister(kClass: KClass<*>, kProperty: KProperty<*>): DataFetcherFactory<Any?> {
23+
getters.computeIfAbsent("${kClass.java.name}.${kProperty.name}") {
24+
kProperty.getter
25+
}
26+
return factory
27+
}
28+
29+
override fun get(
30+
fieldDefinition: GraphQLFieldDefinition,
31+
sourceObject: Any?,
32+
environmentSupplier: Supplier<DataFetchingEnvironment>
33+
): Any? =
34+
sourceObject?.let {
35+
getters["${sourceObject.javaClass.name}.${fieldDefinition.name}"]?.call(sourceObject)
36+
}
37+
38+
override fun get(environment: DataFetchingEnvironment): Any? =
39+
environment.getSource<Any?>()?.let { sourceObject ->
40+
getters["${sourceObject.javaClass.name}.${environment.fieldDefinition.name}"]?.call(sourceObject)
41+
}
42+
}

generator/graphql-kotlin-schema-generator/src/test/kotlin/com/expediagroup/graphql/generator/ToSchemaTest.kt

+111-73
Large diffs are not rendered by default.

generator/graphql-kotlin-schema-generator/src/test/kotlin/com/expediagroup/graphql/generator/testSchemaConfig.kt

+8-1
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,19 @@ package com.expediagroup.graphql.generator
1818

1919
import com.expediagroup.graphql.generator.directives.KotlinDirectiveWiringFactory
2020
import com.expediagroup.graphql.generator.directives.KotlinSchemaDirectiveWiring
21+
import com.expediagroup.graphql.generator.execution.KotlinDataFetcherFactoryProvider
22+
import com.expediagroup.graphql.generator.execution.SimpleKotlinDataFetcherFactoryProvider
2123
import com.expediagroup.graphql.generator.hooks.SchemaGeneratorHooks
2224
import io.mockk.every
2325
import io.mockk.spyk
2426

2527
val defaultSupportedPackages = listOf("com.expediagroup.graphql.generator")
26-
fun testSchemaConfig() = SchemaGeneratorConfig(defaultSupportedPackages)
28+
fun testSchemaConfig(
29+
dataFetcherFactoryProvider: KotlinDataFetcherFactoryProvider = SimpleKotlinDataFetcherFactoryProvider()
30+
) = SchemaGeneratorConfig(
31+
defaultSupportedPackages,
32+
dataFetcherFactoryProvider = dataFetcherFactoryProvider
33+
)
2734

2835
fun getTestSchemaConfigWithHooks(hooks: SchemaGeneratorHooks) = SchemaGeneratorConfig(defaultSupportedPackages, hooks = hooks)
2936

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2022 Expedia, Inc
2+
* Copyright 2025 Expedia, Inc
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.

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

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023 Expedia, Inc
2+
* Copyright 2025 Expedia, Inc
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,18 +17,30 @@
1717
package com.expediagroup.graphql.server.spring.execution
1818

1919
import com.expediagroup.graphql.generator.execution.SimpleKotlinDataFetcherFactoryProvider
20+
import com.expediagroup.graphql.generator.execution.SimpleSingletonKotlinDataFetcherFactoryProvider
2021
import graphql.schema.DataFetcherFactory
2122
import org.springframework.context.ApplicationContext
2223
import kotlin.reflect.KClass
2324
import kotlin.reflect.KFunction
2425

2526
/**
2627
* This provides a wrapper around the [SimpleKotlinDataFetcherFactoryProvider] to call the [SpringDataFetcher] on functions.
27-
* This allows you to use Spring beans as function arugments and they will be populated by the data fetcher.
28+
* This allows you to use Spring beans as function arguments, and they will be populated by the data fetcher.
2829
*/
2930
open class SpringKotlinDataFetcherFactoryProvider(
3031
private val applicationContext: ApplicationContext
3132
) : SimpleKotlinDataFetcherFactoryProvider() {
3233
override fun functionDataFetcherFactory(target: Any?, kClass: KClass<*>, kFunction: KFunction<*>): DataFetcherFactory<Any?> =
3334
DataFetcherFactory { SpringDataFetcher(target, kFunction, applicationContext) }
3435
}
36+
37+
/**
38+
* This provides a wrapper around the [SimpleSingletonKotlinDataFetcherFactoryProvider] to call the [SpringDataFetcher] on functions.
39+
* This allows you to use Spring beans as function arguments, and they will be populated by the data fetcher.
40+
*/
41+
open class SpringSingletonKotlinDataFetcherFactoryProvider(
42+
private val applicationContext: ApplicationContext
43+
) : SimpleSingletonKotlinDataFetcherFactoryProvider() {
44+
override fun functionDataFetcherFactory(target: Any?, kClass: KClass<*>, kFunction: KFunction<*>): DataFetcherFactory<Any?> =
45+
DataFetcherFactory { SpringDataFetcher(target, kFunction, applicationContext) }
46+
}

0 commit comments

Comments
 (0)