Skip to content

Commit 1615bcb

Browse files
authored
Update spring-server to use FlowSubscriptionSchemaGeneratorHooks (ExpediaGroup#1479)
Currently the `spring-server` automatically configures the subscription strategy that supports Kotlin `Flow` but does not configure the hooks neccessary to use it. Updating `SubscriptionAutoConfiguration` to create `FlowSubscriptionSchemaGeneratorHooks` if bean is not available. Cleaned up the federation docs to highlight that both hooks and execution strategy are needed for using `Flow`s in subscriptions. Added a note that the subscription protocol implemented in `spring-server` is deprecated.
1 parent 5f09656 commit 1615bcb

File tree

6 files changed

+65
-20
lines changed

6 files changed

+65
-20
lines changed

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

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

1919
import com.expediagroup.graphql.dataloader.KotlinDataLoaderRegistryFactory
20+
import com.expediagroup.graphql.generator.hooks.FlowSubscriptionSchemaGeneratorHooks
21+
import com.expediagroup.graphql.generator.hooks.SchemaGeneratorHooks
2022
import com.expediagroup.graphql.server.operations.Subscription
2123
import com.expediagroup.graphql.server.spring.subscriptions.ApolloSubscriptionHooks
2224
import com.expediagroup.graphql.server.spring.subscriptions.ApolloSubscriptionProtocolHandler
@@ -54,6 +56,10 @@ private const val URL_HANDLER_ORDER = 0
5456
@Import(GraphQLSchemaConfiguration::class)
5557
class SubscriptionAutoConfiguration {
5658

59+
@Bean
60+
@ConditionalOnMissingBean
61+
fun flowSubscriptionSchemaGeneratorHooks(): SchemaGeneratorHooks = FlowSubscriptionSchemaGeneratorHooks()
62+
5763
@Bean
5864
@ConditionalOnMissingBean
5965
fun subscriptionHandler(

servers/graphql-kotlin-spring-server/src/test/kotlin/com/expediagroup/graphql/server/spring/SubscriptionConfigurationTest.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 Expedia, Inc
2+
* Copyright 2022 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,6 +17,8 @@
1717
package com.expediagroup.graphql.server.spring
1818

1919
import com.expediagroup.graphql.generator.SchemaGeneratorConfig
20+
import com.expediagroup.graphql.generator.hooks.FlowSubscriptionSchemaGeneratorHooks
21+
import com.expediagroup.graphql.generator.hooks.SchemaGeneratorHooks
2022
import com.expediagroup.graphql.server.execution.GraphQLRequestHandler
2123
import com.expediagroup.graphql.server.operations.Query
2224
import com.expediagroup.graphql.server.operations.Subscription
@@ -41,6 +43,7 @@ import java.time.Duration
4143
import kotlin.random.Random
4244
import kotlin.test.assertEquals
4345
import kotlin.test.assertNotNull
46+
import kotlin.test.assertTrue
4447

4548
class SubscriptionConfigurationTest {
4649

@@ -55,6 +58,7 @@ class SubscriptionConfigurationTest {
5558
assertThat(ctx).hasSingleBean(SchemaGeneratorConfig::class.java)
5659
val schemaGeneratorConfig = ctx.getBean(SchemaGeneratorConfig::class.java)
5760
assertEquals(listOf("com.expediagroup.graphql.server.spring"), schemaGeneratorConfig.supportedPackages)
61+
assertTrue(schemaGeneratorConfig.hooks is FlowSubscriptionSchemaGeneratorHooks)
5862

5963
assertThat(ctx).hasSingleBean(GraphQLSchema::class.java)
6064
val schema = ctx.getBean(GraphQLSchema::class.java)
@@ -80,6 +84,9 @@ class SubscriptionConfigurationTest {
8084
.run { ctx ->
8185
val customConfiguration = ctx.getBean(CustomSubscriptionConfiguration::class.java)
8286

87+
assertThat(ctx).hasSingleBean(SchemaGeneratorHooks::class.java)
88+
assertThat(ctx).getBean(SchemaGeneratorHooks::class.java)
89+
.isSameAs(customConfiguration.customHooks())
8390
assertThat(ctx).hasSingleBean(SchemaGeneratorConfig::class.java)
8491
assertThat(ctx).hasSingleBean(GraphQLSchema::class.java)
8592

@@ -114,6 +121,11 @@ class SubscriptionConfigurationTest {
114121
@Configuration
115122
class CustomSubscriptionConfiguration {
116123

124+
@Bean
125+
fun customHooks(): SchemaGeneratorHooks = object : FlowSubscriptionSchemaGeneratorHooks() {
126+
// custom hooks
127+
}
128+
117129
// in regular apps object mapper will be created by JacksonAutoConfiguration
118130
@Bean
119131
fun objectMapper(): ObjectMapper = jacksonObjectMapper()

website/docs/schema-generator/execution/subscriptions.md

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ title: Subscriptions
44
---
55
Subscriptions are supported with `graphql-java`. See their documentation first:
66

7-
https://www.graphql-java.com/documentation/v16/subscriptions/
7+
https://www.graphql-java.com/documentation/subscriptions
88

99
To make a function a subscription function you just have to have the return type wrapped in an implementation of a
1010
reactive-streams `Publisher<T>`. As an example, here is a function that uses Spring WebFlux to return a random number every
@@ -26,23 +26,37 @@ toSchema(
2626
)
2727
```
2828

29-
### Flow Support
29+
## Flow Support
3030

31-
`graphql-kotlin` provides support for Kotlin `Flow` through `FlowSubscriptionExecutionStrategy`. Thanks to the Kotlin
32-
coroutines interoperability, this strategy also works with any `Publisher` and will automatically convert them to a `Flow`.
31+
`graphql-kotlin` provides support for Kotlin `Flow` through `FlowSubscriptionSchemaGeneratorHooks` and `FlowSubscriptionExecutionStrategy`.
32+
Both hooks and execution strategy have to be configured in order to support `Flow` in your GraphQL server.
3333

34-
### Subscription Hooks
34+
`FlowSubscriptionSchemaGeneratorHooks` are custom hooks that provide support for using `Flow` return type within the
35+
GraphQL server.
3536

36-
#### `didGenerateSubscriptionType`
37-
This hook is called after a new subscription type is generated but before it is added to the schema. The other generator hooks are still called so you can add logic for the types and
38-
validation of subscriptions the same as queries and mutations.
37+
`FlowSubscriptionExecutionStrategy` is a reimplementation of the `graphql-java` default `SubscriptionExecutionStrategy`
38+
that adds support for handling Kotlin `Flow` types. Thanks to the Kotlin coroutines interoperability, this strategy works
39+
with any `Publisher` and will automatically convert any `Flow`s to a `Publisher`.
3940

40-
#### `isValidSubscriptionReturnType`
41-
This hook is called when generating the functions for each subscription. It allows for changing the rules of what classes can be used as the return type. By default, graphql-java supports `org.reactivestreams.Publisher`.
41+
## Subscription Hooks
4242

43-
To effectively use this hook, you should also override the `willResolveMonad` hook, and if you are using `graphql-kotlin-spring-server` you should override the `GraphQL` bean to specify a custom subscription execution strategy.
43+
### `willResolveMonad`
4444

45-
### Server Implementation
45+
This hooks is called before resolving Kotlin return type to a GraphQL type and can be used to provide support for additional
46+
monads (e.g. Kotlin `Flow`).
47+
48+
### `didGenerateSubscriptionType`
49+
This hook is called after a new subscription type is generated but before it is added to the schema. The other generator
50+
hooks are still called so you can add logic for the types and validation of subscriptions the same as queries and mutations.
51+
52+
### `isValidSubscriptionReturnType`
53+
This hook is called when generating the functions for each subscription. It allows for changing the rules of what classes
54+
can be used as the return type. By default, graphql-java supports `org.reactivestreams.Publisher`.
55+
56+
To effectively use this hook, you should also override the `willResolveMonad` hook to support the additional subscription
57+
return type. Your GraphQL server may also require a custom subscription execution strategy in order to process it at runtime.
58+
59+
## Server Implementation
4660

4761
The server that runs your GraphQL schema will have to support some method for subscriptions, like WebSockets.
4862
`graphql-kotlin-spring-server` provides a default WebSocket based implementation. See more details in the

website/docs/schema-generator/federation/apollo-federation.mdx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ This class extends the [base configuration class](../customizing-schemas/generat
9090
You can see the definition for `toFederatedSchema` [in the
9191
source](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/generator/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/generator/federation/toFederatedSchema.kt).
9292

93-
## Example
93+
### Example
9494

9595
```kotlin
9696
@KeyDirective(fields = FieldSet("id"))
@@ -146,3 +146,8 @@ type User @key(fields : "id") {
146146
name: String!
147147
}
148148
```
149+
150+
## Limitations
151+
152+
Apollo Federation currently does not support subscriptions. See [Apollo blog](https://www.apollographql.com/blog/backend/federation/using-subscriptions-with-your-federated-data-graph/)
153+
for a workaround.

website/docs/server/server-subscriptions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ If you are using one of the official server implementations for GraphQL Kotlin,
66

77
- See `graphql-kotlin-spring-server` [subscriptions](spring-server/spring-subscriptions.md)
88

9-
Subscriptions require a more indepth knoweldge of how the specific server library handles protocols and streaming.
9+
Subscriptions require a more in-depth knowledge of how the specific server library handles protocols and streaming.
1010
Since we can only support `Publisher` from `graphql-java` in this common library, we can not provide any common logic for subscriptions.
11-
Therefore you will still need to implement the route and request handling for subscriptions separately if you are not using a provided server implementation.
11+
Therefore, you will still need to implement the route and request handling for subscriptions separately if you are not using a provided server implementation.

website/docs/server/spring-server/spring-subscriptions.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,20 @@ This page lists the `graphql-kotlin-spring-server` specific features._
77

88
## Flow Support
99

10-
`graphql-kotlin-spring-server` provides automatic support for Kotlin `Flow` through `FlowSubscriptionExecutionStrategy`
11-
that supports existing `Publisher`s and relies on Kotlin reactive-streams interop to convert `Flow` to a `Publisher`.
10+
`graphql-kotlin-spring-server` provides automatic support for Kotlin `Flow` by automatically configuring `FlowSubscriptionSchemaGeneratorHooks`
11+
and `FlowSubscriptionExecutionStrategy` beans.
1212

13-
## `graphql-ws` subprotocol
13+
:::info
14+
If you define your subscriptions using Kotlin `Flow`, make sure to extend `FlowSubscriptionSchemaGeneratorHooks` whenever you need to provide some custom hooks.
15+
:::
1416

15-
We have implemented subscriptions in Spring WebSockets following the [`graphql-ws` subprotocol](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md) defined by Apollo.
17+
## `subscriptions-transport-ws` subprotocol
18+
19+
:::caution
20+
`subscriptions-transport-ws` was deprecated in favor of [`graphql-ws` protocol](https://github.com/enisdenjo/graphql-ws).
21+
:::
22+
23+
We have implemented subscriptions in Spring WebSockets following the [`subscriptions-transport-ws` subprotocol](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md) defined by Apollo.
1624
This requires that your client send and parse messages in a specific format.
1725

1826
If you would like to implement your own subscription handler, you can provide a primary spring bean for `HandlerMapping` that overrides the [default one](./spring-beans.md) which sets the url for subscriptions to the Apollo subscription handler.

0 commit comments

Comments
 (0)