Skip to content

Commit fb32507

Browse files
Merge pull request #593 from phillip-kruger/subscription
New feature: Subscriptions
2 parents b713a73 + b83b0db commit fb32507

14 files changed

Lines changed: 909 additions & 6 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ server/spec/target/
2121
server/target/
2222
server/tck/target/
2323
spec/target/
24-
tck/target/
24+
tck/target/
25+
/.claude/
26+
/CLAUDE.md
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright (c) 2020 Contributors to the Eclipse Foundation
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+
* http://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 org.eclipse.microprofile.graphql;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
/**
26+
* Specifies that the annotated method provides the implementation (ie. the resolver) for a GraphQL subscription. <br>
27+
* <br>
28+
* For example, a user might annotate a method as such:
29+
*
30+
* <pre>
31+
* public class StockService {
32+
* {@literal @}Subscription("stockQuote")
33+
* {@literal @}Description("Get stock quote changes as they happen")
34+
* public Flow.Publisher{@literal <}Stock{@literal >} getStockQuote(String stockCode) {
35+
* //...
36+
* }
37+
* }
38+
* </pre>
39+
*
40+
* Schema generation of this would result in a stanza such as:
41+
*
42+
* <pre>
43+
* type Subscription {
44+
* "Get stock quote changes as they happen"
45+
* stockQuote(stockCode: String): Stock
46+
* }
47+
* </pre>
48+
*/
49+
@Target(ElementType.METHOD)
50+
@Retention(RetentionPolicy.RUNTIME)
51+
@Documented
52+
public @interface Subscription {
53+
/**
54+
* @return the name to use for the subscription. If empty, annotated method's name is used.
55+
*/
56+
String value() default "";
57+
}

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
<version.testng>7.12.0</version.testng>
3838
<version.arquillian>1.10.0.Final</version.arquillian>
3939
<version.shrinkwrap>1.2.6</version.shrinkwrap>
40+
<version.vertx>4.5.24</version.vertx>
4041
</properties>
4142

4243
<dependencyManagement>
@@ -58,6 +59,11 @@
5859
<artifactId>shrinkwrap-api</artifactId>
5960
<version>${version.shrinkwrap}</version>
6061
</dependency>
62+
<dependency>
63+
<groupId>io.vertx</groupId>
64+
<artifactId>vertx-core</artifactId>
65+
<version>${version.vertx}</version>
66+
</dependency>
6167
</dependencies>
6268
</dependencyManagement>
6369

spec/src/main/asciidoc/components.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ include::components/queries.asciidoc[]
2424

2525
include::components/mutations.asciidoc[]
2626

27+
include::components/subscriptions.asciidoc[]
28+
2729
include::components/generated_schema.asciidoc[]
2830

2931
include::components/arguments.asciidoc[]

spec/src/main/asciidoc/components/mutations.asciidoc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,9 @@ annotation value to the mutation method. See the example in the Query section fo
106106
Like query methods, mutation methods are expected to return some value, thus it is considered a deployment error for a
107107
method with a `void` return type to be annotated with `@Mutation`. If a void method is annotated with the `@Mutation`
108108
annotation, the implementation must prevent the application from starting and should provide a log message indicating
109-
that this is not allowed.
109+
that this is not allowed.
110+
111+
==== Transport Mechanism
112+
113+
Mutation operations are executed over HTTP using the same transport mechanism as queries. See the Transport Mechanism
114+
section in <<queries>> for details on the GraphQL over HTTP specification compliance.

spec/src/main/asciidoc/components/queries.asciidoc

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,15 @@ type Query {
121121
By it's very nature, query methods must return some value, thus it is considered a deployment error for a method with a
122122
`void` return type to be annotated with `@Query`. If a void method is annotated with the `@Query` annotation, the
123123
implementation must prevent the application from starting and should provide a log message indicating that this is not
124-
allowed.
124+
allowed.
125+
126+
==== Transport Mechanism
127+
128+
Query operations are executed over HTTP. Implementations should follow the GraphQL over HTTP specification
129+
(https://github.com/graphql/graphql-over-http) which provides guidelines for executing GraphQL operations via HTTP,
130+
including request format, response format, and status codes. While this specification is currently in draft status,
131+
it represents community consensus on HTTP transport for GraphQL queries and mutations.
132+
133+
Implementations must support POST requests with JSON-encoded bodies containing the GraphQL query, variables, and
134+
operation name. Support for GET requests and other HTTP features described in the GraphQL over HTTP specification
135+
is recommended but not required.
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
//
2+
// Copyright (c) 2020 Contributors to the Eclipse Foundation
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+
// http://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+
[[subscriptions]]
18+
=== Subscriptions
19+
20+
Subscriptions enable clients to receive real-time updates when events occur. Unlike queries and mutations which return
21+
a single response, subscriptions establish a long-lived connection that sends multiple values over time.
22+
23+
==== API Annotation
24+
25+
For classes that are annotated with `@GraphQLApi`, implementations must create a subscription in the schema for every
26+
method that is annotated with `@Subscription`.
27+
28+
The subscription's name can be specified in the value parameter of the `@Subscription` annotation, or is generated from
29+
the method name if no annotation value is provided.
30+
31+
==== Return Types
32+
33+
Subscription methods must return a reactive type that emits a stream of values over time. The MicroProfile GraphQL
34+
specification requires implementations to support `java.util.concurrent.Flow.Publisher<T>` as the return type.
35+
Implementations may provide support for additional reactive types (such as reactive streams `Publisher<T>` or
36+
framework-specific types).
37+
38+
==== Basic POJO Example
39+
40+
.Example
41+
[source,java,numbered]
42+
----
43+
@Subscription
44+
public Flow.Publisher<SuperHero> heroUpdates() {
45+
// Return a publisher that emits SuperHero updates
46+
return publisherService.getHeroUpdateStream();
47+
}
48+
----
49+
50+
.Example with Argument
51+
[source,java,numbered]
52+
----
53+
@Subscription
54+
public Flow.Publisher<SuperHero> heroByName(@Name("name") String heroName) {
55+
return publisherService.getHeroUpdateStream(heroName);
56+
}
57+
----
58+
59+
Note that generic types other than subtypes of `java.util.Collection` (such as `java.util.List` or `java.util.Set`)
60+
are not allowed to be specified within the publisher's generic type parameter. Implementations may allow additional
61+
types (such as `java.util.Map`), but the behavior for these return types are undefined.
62+
63+
==== Name
64+
65+
The name of a subscription in the schema is obtained using the following order:
66+
67+
* if the method is annotated with a `@Subscription` annotation containing a non-empty String for it's value, that
68+
String value is used as the subscription name.
69+
* if the method is annotated with a `@Name` annotation containing a non-empty String for it's value, that String value
70+
is used as the subscription name.
71+
* if the method is annotated with a `@JsonbProperty` annotation containing a non-empty String for it's value, that
72+
String value is used as the subscription name.
73+
* if no other name can be determined, the Java method name is used as the subscription name. (with the get/is removed
74+
if this is a getter)
75+
76+
Note that it is considered a deployment error if more than one subscription method has the same name with the same
77+
arguments.
78+
79+
==== Description
80+
81+
Subscriptions may be documented with descriptions in the schema by adding a `@Description` annotation with
82+
documentation text as the annotation value to the subscription method. For example:
83+
84+
.DescriptionExample
85+
[source,java,numbered]
86+
----
87+
@Subscription
88+
@Description("Get real-time stock price updates")
89+
public Flow.Publisher<Stock> stockQuote(@Name("stockCode") String code) {
90+
return stockService.getPriceUpdates(code);
91+
}
92+
----
93+
94+
This would generate a schema that would include:
95+
96+
.DescriptionSchemaExample
97+
[source,numbered]
98+
----
99+
type Subscription {
100+
...
101+
"Get real-time stock price updates"
102+
stockQuote(stockCode: String): Stock
103+
#...
104+
----
105+
106+
The `@Description` annotation can also be placed on parameters of a subscription method to provide documentation for
107+
the arguments. For example:
108+
109+
.ArgumentDescriptionExample
110+
[source,java,numbered]
111+
----
112+
@Subscription
113+
@Description("Get real-time stock price updates")
114+
public Flow.Publisher<Stock> stockQuote(@Name("stockCode") @Description("Stock symbol code") String code) {
115+
return stockService.getPriceUpdates(code);
116+
}
117+
----
118+
119+
This would generate a schema that would include:
120+
121+
.ArgumentDescriptionSchemaExample
122+
[source,numbered]
123+
----
124+
type Subscription {
125+
...
126+
"Get real-time stock price updates"
127+
stockQuote(
128+
"Stock symbol code"
129+
stockCode: String
130+
): Stock
131+
#...
132+
----
133+
134+
==== Void Subscriptions
135+
136+
By its very nature, subscription methods must return some value stream, thus it is considered a deployment error for a
137+
method with a `void` return type to be annotated with `@Subscription`. If a void method is annotated with the
138+
`@Subscription` annotation, the implementation must prevent the application from starting and should provide a log
139+
message indicating that this is not allowed.
140+
141+
==== Transport Mechanism
142+
143+
Unlike queries and mutations which can be executed over HTTP (see <<queries>> and <<mutations>>), subscriptions require
144+
a persistent connection to stream multiple values over time. To ensure portability across implementations, this
145+
specification mandates support for WebSocket as the transport protocol.
146+
147+
Implementations must support the graphql-ws protocol version 6.0.x
148+
(https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md) for subscription operations over WebSocket.
149+
This protocol provides a standardized way to:
150+
151+
* Establish and maintain subscription connections
152+
* Send subscription requests with operation, variables, and extensions
153+
* Receive streamed subscription events
154+
* Handle errors and connection lifecycle
155+
156+
Implementations may optionally support additional transport mechanisms such as Server-Sent Events (SSE), chunked
157+
transfer encoding, or other streaming protocols, but WebSocket support using the graphql-ws protocol version 6.0.x
158+
is required as the minimum baseline for interoperability.

spec/src/main/asciidoc/intro.asciidoc

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,9 @@ sources with a single API.
139139
The intent of the MicroProfile GraphQL specification is to provide a "code-first" set of APIs that will enable users to
140140
quickly develop portable GraphQL-based applications in Java.
141141

142-
There are 2 main requirements for all implementations of this specification, namely:
142+
There are 3 main requirements for all implementations of this specification, namely:
143143

144-
* Generate and make the GraphQL Schema available. This is done by looking at the annotations in the users code,
145-
and must include all GraphQL Queries and Mutations as well as all entities as defined implicitly via the response type or argument(s) of Queries and Mutations.
144+
* Generate and make the GraphQL Schema available. This is done by looking at the annotations in the users code,
145+
and must include all GraphQL Queries, Mutations, and Subscriptions as well as all entities as defined implicitly via the response type or argument(s) of Queries, Mutations, and Subscriptions.
146146
* Execute GraphQL requests. This will be in the form of either a Query or a Mutation. As a minimum the specification must support executing these requests via HTTP.
147+
* Execute GraphQL Subscriptions. As a minimum the specification must support executing subscription requests via WebSocket using the graphql-ws protocol (https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md).

spec/src/main/asciidoc/release_notes.asciidoc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@
1818
// Contributors:
1919
// Jean-Francois James, Phillip Krüger, Andy McCright, Jean-Baptiste Roux, Bojan Tomic, Adam Anderson
2020

21+
[[release_notes_21]]
22+
== Release Notes for MicroProfile GraphQL 2.1
23+
24+
==== API/SPI Changes
25+
26+
* Added support for GraphQL Subscriptions via `@Subscription` annotation
27+
- Subscription methods return `Flow.Publisher<T>` for streaming real-time updates
28+
- Implementations may support additional reactive types
29+
- Schema generation includes `type Subscription { ... }` definitions
30+
2131
[[release_notes_20]]
2232
== Release Notes for MicroProfile GraphQL 2.0
2333

tck/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@
5555
<artifactId>jakarta.json-api</artifactId>
5656
<scope>provided</scope>
5757
</dependency>
58+
<dependency>
59+
<groupId>io.vertx</groupId>
60+
<artifactId>vertx-core</artifactId>
61+
<scope>provided</scope>
62+
</dependency>
5863

5964
<dependency>
6065
<groupId>org.jboss.arquillian.testng</groupId>

0 commit comments

Comments
 (0)