Skip to content

Commit cb1cc33

Browse files
authored
feature: abstract away creation of GraphQL context (#346)
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 cb1cc33

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
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
}
Lines changed: 45 additions & 0 deletions
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
*/
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 {

0 commit comments

Comments
 (0)