Skip to content

Commit 22138e9

Browse files
committed
feature: abstract away creation of GraphQL context
Abstracts creation of GraphQL context by introducing GraphQLContextFactory interface and provides default WebFilter that utilizes it to populate GraphQL context in the reactor subscriber context.
1 parent 4a179f5 commit 22138e9

File tree

15 files changed

+225
-52
lines changed

15 files changed

+225
-52
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.expediagroup.graphql.sample.context
2+
3+
import com.expediagroup.graphql.spring.execution.GraphQLContextFactory
4+
import org.springframework.http.server.reactive.ServerHttpRequest
5+
import org.springframework.http.server.reactive.ServerHttpResponse
6+
import org.springframework.stereotype.Component
7+
8+
/**
9+
* [GraphQLContextFactory] that generates [MyGraphQLContext] that will be available when processing GraphQL requests.
10+
*/
11+
@Component
12+
class MyGraphQLContextFactory: GraphQLContextFactory<MyGraphQLContext> {
13+
14+
override fun generateContext(request: ServerHttpRequest, response: ServerHttpResponse): MyGraphQLContext = MyGraphQLContext(
15+
myCustomValue = request.headers.getFirst("MyHeader") ?: "defaultContext",
16+
request = request,
17+
response = response)
18+
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
package com.expediagroup.graphql.spring
1818

1919
import com.expediagroup.graphql.spring.exception.KotlinDataFetcherExceptionHandler
20+
import com.expediagroup.graphql.spring.execution.ContextWebFilter
21+
import com.expediagroup.graphql.spring.execution.EmptyContextFactory
22+
import com.expediagroup.graphql.spring.execution.GraphQLContextFactory
23+
import com.expediagroup.graphql.spring.execution.QueryHandler
24+
import com.expediagroup.graphql.spring.execution.SimpleQueryHandler
2025
import graphql.GraphQL
2126
import graphql.execution.AsyncExecutionStrategy
2227
import graphql.execution.AsyncSerialExecutionStrategy
@@ -31,6 +36,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
3136
import org.springframework.context.annotation.Bean
3237
import org.springframework.context.annotation.Configuration
3338
import org.springframework.context.annotation.Import
39+
import org.springframework.web.server.WebFilter
3440
import java.util.Optional
3541

3642
/**
@@ -79,4 +85,11 @@ class GraphQLAutoConfiguration {
7985
@Bean
8086
@ConditionalOnMissingBean
8187
fun graphQLQueryHandler(graphql: GraphQL): QueryHandler = SimpleQueryHandler(graphql)
88+
89+
@Bean
90+
@ConditionalOnMissingBean
91+
fun graphQLContextFactory(): GraphQLContextFactory<*> = EmptyContextFactory
92+
93+
@Bean
94+
fun contextWebFilter(graphQLContextFactory: GraphQLContextFactory<*>): WebFilter = ContextWebFilter(graphQLContextFactory)
8295
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.expediagroup.graphql.spring
1818

1919
import com.expediagroup.graphql.extensions.print
20+
import com.expediagroup.graphql.spring.execution.QueryHandler
2021
import com.expediagroup.graphql.spring.model.GraphQLRequest
2122
import com.fasterxml.jackson.databind.ObjectMapper
2223
import com.fasterxml.jackson.databind.type.MapType

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package com.expediagroup.graphql.spring
1818

19+
import com.expediagroup.graphql.spring.execution.SimpleSubscriptionHandler
20+
import com.expediagroup.graphql.spring.execution.SubscriptionHandler
1921
import com.expediagroup.graphql.spring.operations.Subscription
2022
import com.fasterxml.jackson.databind.ObjectMapper
2123
import graphql.GraphQL

examples/spring/src/main/kotlin/com/expediagroup/graphql/sample/context/MyGraphQLContextWebFilter.kt renamed to graphql-kotlin-spring-server/src/main/kotlin/com/expediagroup/graphql/spring/execution/ContextWebFilter.kt

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

17-
package com.expediagroup.graphql.sample.context
17+
package com.expediagroup.graphql.spring.execution
1818

19-
import com.expediagroup.graphql.spring.GRAPHQL_CONTEXT_KEY
20-
import org.springframework.stereotype.Component
19+
import org.springframework.core.Ordered
20+
import org.springframework.core.annotation.Order
2121
import org.springframework.web.server.ServerWebExchange
2222
import org.springframework.web.server.WebFilter
2323
import org.springframework.web.server.WebFilterChain
2424
import reactor.core.publisher.Mono
2525

2626
/**
27-
* Simple WebFilter that creates custom [MyGraphQLContext] and adds its to the SubscriberContext.
27+
* [org.springframework.core.Ordered] value used for the [ContextWebFilter] order in which it will be applied to the incoming requests.
2828
*/
29-
@Component
30-
class MyGraphQLContextWebFilter : WebFilter {
29+
const val GRAPHQL_CONTEXT_FILTER_ODER = 0
3130

31+
/**
32+
* Default web filter that populates GraphQL context in the reactor subscriber context.
33+
*/
34+
@Order(GRAPHQL_CONTEXT_FILTER_ODER)
35+
class ContextWebFilter(private val contextFactory: GraphQLContextFactory<Any>) : WebFilter, Ordered {
36+
37+
@Suppress("ForbiddenVoid")
3238
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
33-
val myValue = exchange.request.headers.getFirst("MyHeader") ?: "defaultContext"
34-
val customContext = MyGraphQLContext(
35-
myCustomValue = myValue,
36-
request = exchange.request,
37-
response = exchange.response)
38-
return chain.filter(exchange).subscriberContext { it.put(GRAPHQL_CONTEXT_KEY, customContext) }
39+
val context = contextFactory.generateContext(exchange.request, exchange.response)
40+
return chain.filter(exchange).subscriberContext { it.put(GRAPHQL_CONTEXT_KEY, context) }
3941
}
42+
43+
override fun getOrder(): Int = GRAPHQL_CONTEXT_FILTER_ODER
4044
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2019 Expedia, Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.expediagroup.graphql.spring.execution
18+
19+
import graphql.GraphQLContext
20+
import org.springframework.http.server.reactive.ServerHttpRequest
21+
import org.springframework.http.server.reactive.ServerHttpResponse
22+
23+
/**
24+
* Reactor SubscriberContext key for storing GraphQL context.
25+
*/
26+
const val GRAPHQL_CONTEXT_KEY = "graphQLContext"
27+
28+
/**
29+
* Factory that generates GraphQL context.
30+
*/
31+
interface GraphQLContextFactory<out T : Any> {
32+
33+
/**
34+
* Generate GraphQL context based on the incoming request and the corresponding response.
35+
*/
36+
fun generateContext(request: ServerHttpRequest, response: ServerHttpResponse): T
37+
}
38+
39+
/**
40+
* Default context factory that generates empty GraphQL context.
41+
*/
42+
internal object EmptyContextFactory : GraphQLContextFactory<GraphQLContext> {
43+
44+
override fun generateContext(request: ServerHttpRequest, response: ServerHttpResponse): GraphQLContext = GraphQLContext.newContext().build()
45+
}

graphql-kotlin-spring-server/src/main/kotlin/com/expediagroup/graphql/spring/QueryHandler.kt renamed to graphql-kotlin-spring-server/src/main/kotlin/com/expediagroup/graphql/spring/execution/QueryHandler.kt

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

17-
package com.expediagroup.graphql.spring
17+
package com.expediagroup.graphql.spring.execution
1818

1919
import com.expediagroup.graphql.spring.exception.SimpleKotlinGraphQLError
2020
import com.expediagroup.graphql.spring.model.GraphQLRequest
@@ -27,11 +27,6 @@ import kotlinx.coroutines.future.await
2727
import kotlinx.coroutines.reactor.ReactorContext
2828
import kotlin.coroutines.coroutineContext
2929

30-
/**
31-
* Reactor SubscriberContext key for storing GraphQL context.
32-
*/
33-
const val GRAPHQL_CONTEXT_KEY = "graphQLContext"
34-
3530
/**
3631
* GraphQL query handler.
3732
*/

graphql-kotlin-spring-server/src/main/kotlin/com/expediagroup/graphql/spring/SubscriptionHandler.kt renamed to graphql-kotlin-spring-server/src/main/kotlin/com/expediagroup/graphql/spring/execution/SubscriptionHandler.kt

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

17-
package com.expediagroup.graphql.spring
17+
package com.expediagroup.graphql.spring.execution
1818

1919
import com.expediagroup.graphql.spring.model.GraphQLRequest
2020
import com.expediagroup.graphql.spring.model.GraphQLResponse
Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.expediagroup.graphql.spring.federation
1+
package com.expediagroup.graphql.spring
22

33
import com.expediagroup.graphql.SchemaGeneratorConfig
44
import com.expediagroup.graphql.TopLevelObject
@@ -9,8 +9,8 @@ import com.expediagroup.graphql.federation.directives.ExternalDirective
99
import com.expediagroup.graphql.federation.directives.FieldSet
1010
import com.expediagroup.graphql.federation.directives.KeyDirective
1111
import com.expediagroup.graphql.federation.execution.FederatedTypeRegistry
12-
import com.expediagroup.graphql.spring.GraphQLAutoConfiguration
13-
import com.expediagroup.graphql.spring.QueryHandler
12+
import com.expediagroup.graphql.spring.execution.GraphQLContextFactory
13+
import com.expediagroup.graphql.spring.execution.QueryHandler
1414
import com.expediagroup.graphql.spring.operations.Query
1515
import com.expediagroup.graphql.toSchema
1616
import com.fasterxml.jackson.databind.ObjectMapper
@@ -35,11 +35,11 @@ class FederationConfigurationTest {
3535
@Test
3636
fun `verify federated schema auto configuration`() {
3737
contextRunner.withUserConfiguration(FederatedConfiguration::class.java)
38-
.withPropertyValues("graphql.packages=com.expediagroup.graphql.spring.federation", "graphql.federation.enabled=true")
38+
.withPropertyValues("graphql.packages=com.expediagroup.graphql.spring", "graphql.federation.enabled=true")
3939
.run { ctx ->
4040
assertThat(ctx).hasSingleBean(SchemaGeneratorConfig::class.java)
4141
val schemaGeneratorConfig = ctx.getBean(SchemaGeneratorConfig::class.java)
42-
assertEquals(listOf("com.expediagroup.graphql.spring.federation"), schemaGeneratorConfig.supportedPackages)
42+
assertEquals(listOf("com.expediagroup.graphql.spring"), schemaGeneratorConfig.supportedPackages)
4343

4444
assertThat(ctx).hasSingleBean(GraphQLSchema::class.java)
4545
val schema = ctx.getBean(GraphQLSchema::class.java)
@@ -60,6 +60,7 @@ class FederationConfigurationTest {
6060

6161
assertThat(ctx).hasSingleBean(GraphQL::class.java)
6262
assertThat(ctx).hasSingleBean(QueryHandler::class.java)
63+
assertThat(ctx).hasSingleBean(GraphQLContextFactory::class.java)
6364
}
6465
}
6566

@@ -79,12 +80,14 @@ class FederationConfigurationTest {
7980

8081
assertThat(ctx).hasSingleBean(GraphQL::class.java)
8182
assertThat(ctx).hasSingleBean(QueryHandler::class.java)
83+
assertThat(ctx).hasSingleBean(GraphQLContextFactory::class.java)
8284
}
8385
}
8486

8587
@Configuration
8688
class FederatedConfiguration {
8789

90+
// in regular apps object mapper will be created by JacksonAutoConfiguration
8891
@Bean
8992
fun objectMapper(): ObjectMapper = jacksonObjectMapper()
9093

@@ -95,6 +98,7 @@ class FederationConfigurationTest {
9598
@Configuration
9699
class CustomFederatedConfiguration {
97100

101+
// in regular apps object mapper will be created by JacksonAutoConfiguration
98102
@Bean
99103
fun objectMapper(): ObjectMapper = jacksonObjectMapper()
100104

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
package com.expediagroup.graphql.spring.base
1+
package com.expediagroup.graphql.spring
22

33
import com.expediagroup.graphql.SchemaGeneratorConfig
44
import com.expediagroup.graphql.TopLevelObject
5-
import com.expediagroup.graphql.spring.GraphQLAutoConfiguration
6-
import com.expediagroup.graphql.spring.QueryHandler
5+
import com.expediagroup.graphql.spring.execution.GraphQLContextFactory
6+
import com.expediagroup.graphql.spring.execution.QueryHandler
77
import com.expediagroup.graphql.spring.operations.Query
88
import com.expediagroup.graphql.toSchema
99
import com.fasterxml.jackson.databind.ObjectMapper
@@ -13,6 +13,7 @@ import graphql.execution.instrumentation.Instrumentation
1313
import graphql.execution.instrumentation.tracing.TracingInstrumentation
1414
import graphql.schema.GraphQLSchema
1515
import graphql.schema.GraphQLTypeUtil
16+
import io.mockk.mockk
1617
import org.assertj.core.api.AssertionsForInterfaceTypes.assertThat
1718
import org.junit.jupiter.api.Test
1819
import org.springframework.boot.autoconfigure.AutoConfigurations
@@ -31,11 +32,11 @@ class SchemaConfigurationTest {
3132
@Test
3233
fun `verify schema auto configuration`() {
3334
contextRunner.withUserConfiguration(BasicConfiguration::class.java)
34-
.withPropertyValues("graphql.packages=com.expediagroup.graphql.spring.base")
35+
.withPropertyValues("graphql.packages=com.expediagroup.graphql.spring")
3536
.run { ctx ->
3637
assertThat(ctx).hasSingleBean(SchemaGeneratorConfig::class.java)
3738
val schemaGeneratorConfig = ctx.getBean(SchemaGeneratorConfig::class.java)
38-
assertEquals(listOf("com.expediagroup.graphql.spring.base"), schemaGeneratorConfig.supportedPackages)
39+
assertEquals(listOf("com.expediagroup.graphql.spring"), schemaGeneratorConfig.supportedPackages)
3940

4041
assertThat(ctx).hasSingleBean(GraphQLSchema::class.java)
4142
val schema = ctx.getBean(GraphQLSchema::class.java)
@@ -56,6 +57,7 @@ class SchemaConfigurationTest {
5657
assertNotNull(result["extensions"])
5758

5859
assertThat(ctx).hasSingleBean(QueryHandler::class.java)
60+
assertThat(ctx).hasSingleBean(GraphQLContextFactory::class.java)
5961
}
6062
}
6163

@@ -78,12 +80,17 @@ class SchemaConfigurationTest {
7880
.isSameAs(customConfiguration.myGraphQL())
7981

8082
assertThat(ctx).hasSingleBean(QueryHandler::class.java)
83+
84+
assertThat(ctx).hasSingleBean(GraphQLContextFactory::class.java)
85+
assertThat(ctx).getBean(GraphQLContextFactory::class.java)
86+
.isSameAs(customConfiguration.myCustomContextFactory())
8187
}
8288
}
8389

8490
@Configuration
8591
class BasicConfiguration {
8692

93+
// in regular apps object mapper will be created by JacksonAutoConfiguration
8794
@Bean
8895
fun objectMapper(): ObjectMapper = jacksonObjectMapper()
8996

@@ -97,6 +104,7 @@ class SchemaConfigurationTest {
97104
@Configuration
98105
class CustomConfiguration {
99106

107+
// in regular apps object mapper will be created by JacksonAutoConfiguration
100108
@Bean
101109
fun objectMapper(): ObjectMapper = jacksonObjectMapper()
102110

@@ -115,6 +123,9 @@ class SchemaConfigurationTest {
115123
fun myGraphQL(): GraphQL = GraphQL.newGraphQL(mySchema())
116124
.instrumentation(TracingInstrumentation())
117125
.build()
126+
127+
@Bean
128+
fun myCustomContextFactory(): GraphQLContextFactory<Map<String, Any>> = mockk()
118129
}
119130

120131
class BasicQuery : Query {
Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
1-
package com.expediagroup.graphql.spring.subscription
1+
package com.expediagroup.graphql.spring
22

33
import com.expediagroup.graphql.SchemaGeneratorConfig
4-
import com.expediagroup.graphql.federation.directives.ExtendsDirective
5-
import com.expediagroup.graphql.federation.directives.ExternalDirective
6-
import com.expediagroup.graphql.federation.directives.FieldSet
7-
import com.expediagroup.graphql.federation.directives.KeyDirective
8-
import com.expediagroup.graphql.spring.GraphQLAutoConfiguration
9-
import com.expediagroup.graphql.spring.QueryHandler
10-
import com.expediagroup.graphql.spring.SubscriptionHandler
4+
import com.expediagroup.graphql.spring.execution.QueryHandler
5+
import com.expediagroup.graphql.spring.execution.SubscriptionHandler
116
import com.expediagroup.graphql.spring.operations.Subscription
127
import com.fasterxml.jackson.databind.ObjectMapper
138
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
@@ -36,11 +31,11 @@ class SubscriptionConfigurationTest {
3631
@Test
3732
fun `verify subscription auto configuration`() {
3833
contextRunner.withUserConfiguration(SubscriptionConfiguration::class.java)
39-
.withPropertyValues("graphql.packages=com.expediagroup.graphql.spring.subscription")
34+
.withPropertyValues("graphql.packages=com.expediagroup.graphql.spring")
4035
.run { ctx ->
4136
assertThat(ctx).hasSingleBean(SchemaGeneratorConfig::class.java)
4237
val schemaGeneratorConfig = ctx.getBean(SchemaGeneratorConfig::class.java)
43-
assertEquals(listOf("com.expediagroup.graphql.spring.subscription"), schemaGeneratorConfig.supportedPackages)
38+
assertEquals(listOf("com.expediagroup.graphql.spring"), schemaGeneratorConfig.supportedPackages)
4439

4540
assertThat(ctx).hasSingleBean(GraphQLSchema::class.java)
4641
val schema = ctx.getBean(GraphQLSchema::class.java)
@@ -60,7 +55,7 @@ class SubscriptionConfigurationTest {
6055
}
6156

6257
@Test
63-
fun `verify subscriptionauto configuration backs off in beans are defined by user`() {
58+
fun `verify subscription auto configuration backs off in beans are defined by user`() {
6459
contextRunner.withUserConfiguration(CustomSubscriptionConfiguration::class.java)
6560
.run { ctx ->
6661
val customConfiguration = ctx.getBean(CustomSubscriptionConfiguration::class.java)
@@ -85,6 +80,7 @@ class SubscriptionConfigurationTest {
8580
@Configuration
8681
class SubscriptionConfiguration {
8782

83+
// in regular apps object mapper will be created by JacksonAutoConfiguration
8884
@Bean
8985
fun objectMapper(): ObjectMapper = jacksonObjectMapper()
9086

@@ -95,6 +91,7 @@ class SubscriptionConfigurationTest {
9591
@Configuration
9692
class CustomSubscriptionConfiguration {
9793

94+
// in regular apps object mapper will be created by JacksonAutoConfiguration
9895
@Bean
9996
fun objectMapper(): ObjectMapper = jacksonObjectMapper()
10097

@@ -113,8 +110,4 @@ class SubscriptionConfigurationTest {
113110
.delayElements(Duration.ofMillis(100))
114111
.map { Random.nextInt() }
115112
}
116-
117-
@ExtendsDirective
118-
@KeyDirective(fields = FieldSet("id"))
119-
data class Widget(@ExternalDirective val id: Int, val name: String)
120113
}

0 commit comments

Comments
 (0)