GraphQL subscriptions enable clients to receive continual, real-time updates whenever new data becomes available. Unlike queries and mutations, subscriptions are long-lasting. This means a client can receive multiple updates from a single subscription.
Spring GraphQL provides out of box support for GraphQL subscriptions over WebSockets using graphql-transport-ws protocol. This library adds support for subscriptions using Apollo HTTP callback protocol.
See Apollo Router for additional details about Federation and Subscription support.
sequenceDiagram;
Router->>Spring GraphQL Server: Register subscription and callback URL over HTTP
Note over Spring GraphQL Server: Initialize protocol
Spring GraphQL Server->>Router: check
Router->>Spring GraphQL Server: HTTP 204 No Content
Note over Spring GraphQL Server: Start subsciption and heartbeat<br>on background threads
Spring GraphQL Server->>Router: HTTP 200 OK<br>{ "data": null }
Note over Router: Time passes
par Subscription to Router
Note over Subscription: While data available
loop Data Available
Subscription->>Router: next<br>{ "data": <subscriptionData> }
Router->>Subscription: HTTP 200 OK
end
Note over Subscription: Subscription Complete/Error
Subscription->>Router: complete
Router->>Subscription: HTTP 202 Accepted
and Heartbeat to Router
loop Emit every 5 seconds
Heartbeat->>Router: check
Router->>Heartbeat: HTTP 204 No Content
end
end
Federation JVM libraries are published to Maven Central.
Using a JVM dependency manager, link federation-spring-subscription-callback
to your project.
With Maven:
<dependency>
<groupId>com.apollographql.federation</groupId>
<artifactId>federation-spring-subscription-callback</artifactId>
<version>${latestVersion}</version>
</dependency>
With Gradle (Kotlin):
implementation("com.apollographql.federation:federation-spring-subscription-callback:$latestVersion")
In order to enable HTTP subscription callback protocol support you need to configure SubscriptionCallbackHandler
and
CallbackWebGraphQLInterceptor
beans in your application context.
CallbackWebGraphQLInterceptor
works with both WebMVC and WebFlux applications.
Given a subscription
@Controller
public class MySubscriptionController {
@SubscriptionMapping
public Flux<Integer> counter() {
return Flux.just(1, 2, 3, 4, 5, 6)
.delayElements(Duration.ofMillis(200));
}
}
We can enable subscription HTTP callback support using following configuration
@Configuration
public class GraphQLConfiguration {
@Bean
public SubscriptionCallbackHandler callbackHandler(ExecutionGraphQlService graphQlService) {
return new SubscriptionCallbackHandler(graphQlService);
}
// This interceptor defaults to Ordered#LOWEST_PRECEDENCE order as it should run last in chain
// to allow users to still apply other interceptors that handle common stuff (e.g. extracting
// auth headers, etc).
// You can override this behavior by specifying custom order.
@Bean
public CallbackWebGraphQLInterceptor callbackGraphQlInterceptor(
SubscriptionCallbackHandler callbackHandler) {
return new CallbackWebGraphQLInterceptor(callbackHandler);
}
// regular federation transforms
// see https://docs.spring.io/spring-graphql/reference/federation.html
@Bean
public GraphQlSourceBuilderCustomizer customizer(FederationSchemaFactory factory) {
return builder -> builder.schemaFactory(factory::createGraphQLSchema);
}
@Bean
FederationSchemaFactory federationSchemaFactory() {
return new FederationSchemaFactory();
}
}
By default, subscription and heartbeat stream will be executed in non-blocking way on bounded elastic scheduler. If you need more granular control over the underlying scheduler, you can configure callback handler to run on your provided scheduler.
@Bean
public SubscriptionCallbackHandler callbackHandler(ExecutionGraphQlService graphQlService) {
Scheduler customScheduler = <provide your custom scheduler>;
return new SubscriptionCallbackHandler(graphQlService, customScheduler);
}