Skip to content

Commit 127c2c7

Browse files
authored
feat: subscription http callback support for spring graphql (#354)
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](https://docs.spring.io/spring-graphql/reference/) provides out of box support for GraphQL subscriptions over WebSockets using [graphql-transport-ws](https://github.com/enisdenjo/graphql-ws) protocol. This PR adds new `federation-spring-subscription-callback` module that provides support for subscriptions using [Apollo HTTP callback protocol](https://www.apollographql.com/docs/router/executing-operations/subscription-callback-protocol). See * [Apollo Router](https://www.apollographql.com/docs/router/executing-operations/subscription-support) for additional details about Federation and Subscription support
1 parent c00e052 commit 127c2c7

26 files changed

+1844
-164
lines changed

README.md

Lines changed: 17 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
[![MIT License](https://img.shields.io/github/license/apollographql/federation-jvm.svg)](LICENSE)
33
[![Maven Central](https://img.shields.io/maven-central/v/com.apollographql.federation/federation-graphql-java-support.svg)](https://maven-badges.herokuapp.com/maven-central/com.apollographql.federation/federation-graphql-java-support)
44
[![Join the community forum](https://img.shields.io/badge/join%20the%20community-forum-blueviolet)](https://community.apollographql.com)
5+
[![Join our Discord server](https://img.shields.io/discord/1022972389463687228.svg?color=7389D8&labelColor=6A7EC2&logo=discord&logoColor=ffffff&style=flat-square)](https://discord.gg/graphos)
56

67
# Apollo Federation on the JVM
78

89
[**Apollo Federation**](https://www.apollographql.com/docs/federation/) is a powerful, open architecture that helps you create a **unified supergraph** that combines multiple GraphQL APIs.
9-
`graphql-java-support` provides Apollo Federation support for building subgraphs in the `graphql-java` ecosystem. Individual subgraphs can be run independently of each other but can also specify
10+
`federation-graphql-java-support` provides Apollo Federation support for building subgraphs in the `graphql-java` ecosystem. Individual subgraphs can be run independently of each other but can also specify
1011
relationships to the other subgraphs by using Federated directives. See [Apollo Federation documentation](https://www.apollographql.com/docs/federation/) for details.
1112

1213
```mermaid
@@ -18,180 +19,32 @@ graph BT;
1819
gateway --- serviceA & serviceB & serviceC;
1920
```
2021

21-
`graphql-java-support` is built on top of `graphql-java` and provides transformation logic to make your GraphQL schemas Federation compatible. `SchemaTransformer` adds common Federation
22+
## Modules
23+
24+
### Federation JVM Support
25+
26+
`federation-graphql-java-support` is built on top of `graphql-java` and provides transformation logic to make your GraphQL schemas Federation compatible. `SchemaTransformer` adds common Federation
2227
type definitions (e.g. `_Any` scalar, `_Entity` union, Federation directives, etc) and allows you to easily specify your Federated entity resolvers.
2328

2429
This project also provides a set of Federation aware instrumentations:
2530

2631
* `CacheControlInstrumentation` - instrumentation that computes a max age for an operation based on `@cacheControl` directives
2732
* `FederatedTracingInstrumentation` - instrumentation that generates trace information for federated operations
2833

29-
## Installation
30-
31-
Federation JVM libraries are published to [Maven Central](https://search.maven.org/search?q=g:com.apollographql.federation%20AND%20a:federation-graphql-java-support).
32-
Using a JVM dependency manager, link `graphql-java-support` to your project.
33-
34-
With Maven:
35-
36-
```xml
37-
<dependency>
38-
<groupId>com.apollographql.federation</groupId>
39-
<artifactId>federation-graphql-java-support</artifactId>
40-
<version>${latestVersion}</version>
41-
</dependency>
42-
```
43-
44-
With Gradle (Groovy):
45-
46-
```groovy
47-
implementation 'com.apollographql.federation:federation-graphql-java-support:$latestVersion'
48-
```
49-
50-
## Usage
51-
52-
Additional documentation on the Apollo Federation and JVM usage can be found on the [Apollo Documentation Portal](https://www.apollographql.com/docs/federation/).
53-
54-
Federation JVM example integrations
34+
See module [README](graphql-java-support/README.md) for details.
5535

56-
* [Spring GraphQL Federation Example](https://github.com/apollographql/federation-jvm-spring-example)
57-
* [Netflix DGS Federation Example](https://github.com/Netflix/dgs-federation-example)
58-
* [GraphQL Java Kickstart Federation Example](https://github.com/setchy/graphql-java-kickstart-federation-example)
36+
### Subscription HTTP Callback Support for Spring GraphQL
5937

60-
### Creating Federated Schemas
38+
GraphQL subscriptions enable clients to receive continual, real-time updates whenever new data becomes available. Unlike
39+
queries and mutations, subscriptions are long-lasting. This means a client can receive multiple updates from a single subscription.
6140

62-
Using `graphql-java` (or [your](https://docs.spring.io/spring-graphql/docs/current/reference/html/) [framework](https://netflix.github.io/dgs/) of [choice](https://www.graphql-java-kickstart.com/spring-boot/))
63-
we first need to create a GraphQL schema.
41+
[Spring GraphQL](https://docs.spring.io/spring-graphql/reference/) provides out of box support for GraphQL subscriptions
42+
over WebSockets using [graphql-transport-ws](https://github.com/enisdenjo/graphql-ws) protocol. This library adds support
43+
for subscriptions using [Apollo HTTP callback protocol](https://www.apollographql.com/docs/router/executing-operations/subscription-callback-protocol).
6444

65-
Assuming there is already a subgraph that defines a base `Product` type
66-
67-
```graphql
68-
# product subgraph
69-
type Query {
70-
product(id: ID!): Product
71-
}
72-
73-
type Product @key(fields: "id") {
74-
id: ID!,
75-
description: String
76-
}
77-
```
78-
79-
We can create another subgraph that extends `Product` type and adds the `reviews` field.
80-
81-
```graphql
82-
# reviews subgraph
83-
type Product @extends @key(fields: "id") {
84-
id: ID! @external
85-
reviews: [Review!]!
86-
}
87-
88-
type Review {
89-
id: ID!
90-
text: String
91-
rating: Int!
92-
}
93-
```
94-
95-
>NOTE: This subgraph does not specify any top level queries.
96-
97-
Using the above schema file, we first need to generate the `TypeDefinitionRegistry` and `RuntimeWiring` objects.
98-
99-
```java
100-
SchemaParser parser = new SchemaParser();
101-
TypeDefinitionRegistry typeDefinitionRegistry = parser.parse(Paths.get("schema.graphqls").toFile());
102-
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring().build();
103-
```
104-
105-
We can then generate Federation compatible schema using schema transformer. In order to be able to resolve the federated `Product` type, we need to provide `TypeResolver` to resolve [`_Entity`](https://www.apollographql.com/docs/federation/federation-spec/#union-_entity)
106-
union type and a `DataFetcher` to resolve [`_entities`](https://www.apollographql.com/docs/federation/federation-spec/#query_entities) query.
107-
108-
```java
109-
DataFetcher entityDataFetcher = env -> {
110-
List<Map<String, Object>> representations = env.getArgument(_Entity.argumentName);
111-
return representations.stream()
112-
.map(representation -> {
113-
if ("Product".equals(representation.get("__typename"))) {
114-
return new Product((String)representation.get("id"));
115-
}
116-
return null;
117-
})
118-
.collect(Collectors.toList());
119-
};
120-
TypeResolver entityTypeResolver = env -> {
121-
final Object src = env.getObject();
122-
if (src instanceof Product) {
123-
return env.getSchema()
124-
.getObjectType("Product");
125-
}
126-
return null;
127-
};
128-
129-
GraphQLSchema federatedSchema = Federation.transform(typeDefinitionRegistry, runtimeWiring)
130-
.fetchEntities(entityDataFetcher)
131-
.resolveEntityType(entityTypeResolver)
132-
.build();
133-
```
134-
135-
This will generate a schema with additional federated info.
136-
137-
```graphql
138-
union _Entity = Product
139-
140-
type Product @extends @key(fields : "id") {
141-
id: ID! @external
142-
reviews: [Review!]!
143-
}
144-
145-
type Query {
146-
_entities(representations: [_Any!]!): [_Entity]!
147-
_service: _Service
148-
}
149-
150-
type Review {
151-
id: ID!
152-
rating: Int!
153-
text: String
154-
}
155-
156-
type _Service {
157-
sdl: String!
158-
}
159-
160-
scalar _Any
161-
162-
scalar _FieldSet
163-
```
164-
165-
### Instrumentation
166-
167-
#### Federated Tracing
168-
169-
[Tracing your GraphQL queries](https://www.apollographql.com/docs/federation/metrics) can provide you detailed insights into your GraphQL layer's performance and usage. Single federated query may
170-
be executed against multiple GraphQL servers. Apollo Gateway provides ability to aggregate trace data generated by the subgraphs calls and then send them to [Apollo Studio](https://www.apollographql.com/docs/studio/)
171-
172-
To make your server generate performance traces and return them along with responses to the Apollo Gateway, install the `FederatedTracingInstrumentation` into your `GraphQL` object:
173-
174-
```java
175-
GraphQL graphql = GraphQL.newGraphQL(graphQLSchema)
176-
.instrumentation(new FederatedTracingInstrumentation())
177-
.build();
178-
```
179-
180-
**By default, all requests will be traced.** In order to skip dev requests and only trace requests that come from the Apollo Gateway, you should populate tracing information in the `GraphQLContext` map.
181-
This will ensure that only requests with `apollo-federation-include-trace=ftv1` header value will be traced.
182-
183-
```java
184-
String federatedTracingHeaderValue = httpRequest.getHeader(FEDERATED_TRACING_HEADER_NAME);
185-
186-
Map<Object, Object> contextMap = new HashMap<>();
187-
contextMap.put(FEDERATED_TRACING_HEADER_NAME, federatedTracingHeaderValue);
188-
189-
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
190-
.graphQLContext(contextMap)
191-
.query(queryString)
192-
.build();
193-
graphql.executeAsync(executionInput);
194-
```
45+
See [Apollo Router](https://www.apollographql.com/docs/router/executing-operations/subscription-support) for additional
46+
details about Federation and Subscription support. See module [README](spring-subscription-callback/README.md) for library
47+
details.
19548

19649
## Contact
19750

gradle.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@ version = 3.0-SNAPSHOT
44
# dependencies
55
annotationsVersion = 24.0.1
66
graphQLJavaVersion = 21.1
7+
mockWebServerVersion = 4.11.0
78
protobufVersion = 3.24.3
89
slf4jVersion = 2.0.9
10+
springBootVersion = 3.1.4
11+
springGraphQLVersion = 1.2.3
12+
reactorVersion = 3.5.9
913

1014
# test dependencies
1115
junitVersion = 5.10.0

0 commit comments

Comments
 (0)