Skip to content

Commit 0fffc84

Browse files
committed
Support multiple queries in DgsGraphQlClient
Prior to this commit, the `DgsGraphQlClient` would only allow a single query per call. The DGS client natively supports `GraphQLMultiQueryRequest` for sending multiple queries in a single request (provided they each have an alias). This commit allows to chain multiple `request()` calls and turns them into a single request when execution is triggered. Closes gh-1221
1 parent ac601fc commit 0fffc84

File tree

12 files changed

+631
-28
lines changed

12 files changed

+631
-28
lines changed

spring-graphql-docs/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ dependencies {
2929
implementation "org.springframework.boot:spring-boot-starter-web:${springBootVersion}"
3030
implementation 'io.rsocket:rsocket-core'
3131
implementation 'io.rsocket:rsocket-transport-netty'
32+
implementation('com.netflix.graphql.dgs.codegen:graphql-dgs-codegen-shared-core') {
33+
exclude group: "com.apollographql.federation", module: "federation-graphql-java-support"
34+
exclude group: "com.graphql-java"
35+
}
3236
implementation 'io.projectreactor:reactor-test'
3337
implementation 'org.assertj:assertj-core'
3438
}

spring-graphql-docs/modules/ROOT/pages/client.adoc

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -420,17 +420,17 @@ For example, given the following schema:
420420

421421
You can perform a request as follows:
422422

423-
[source,java,indent=0,subs="verbatim,quotes"]
424-
----
425-
HttpGraphQlClient client = ... ;
426-
DgsGraphQlClient dgsClient = DgsGraphQlClient.create(client); // <1>
427-
428-
List<Book> books = dgsClient.request(new BooksGraphQLQuery()) // <2>
429-
.projection(new BooksProjectionRoot<>().id().name()) // <3>
430-
.retrieveSync("books")
431-
.toEntityList(Book.class);
432-
----
433-
434-
<1> - Create `DgsGraphQlClient` by wrapping any `GraphQlClient`.
435-
<2> - Specify the operation for the request.
436-
<3> - Define the selection set.
423+
include-code::DgsClientUsage[tag=sendSingleQuery,indent=0]
424+
<1> Create `DgsGraphQlClient` by wrapping any `GraphQlClient`.
425+
<2> Specify the operation for the request.
426+
<3> Define the selection set.
427+
428+
The `DgsGraphQlClient` also supports multiple queries by chaining `query()` calls:
429+
430+
include-code::DgsClientUsage[tag=sendManyQueries,indent=0]
431+
<1> Create `DgsGraphQlClient` by wrapping any `GraphQlClient`.
432+
<2> Specify the operation for the first request.
433+
<3> When multiple requests are sent, we need to specify an alias for each
434+
<4> Specify the operation for the second request.
435+
<5> Get the complete response
436+
<6> Get the relevant document parts with the configured aliases
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
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 org.springframework.graphql.docs.client.dgsgraphqlclient;
18+
19+
import com.netflix.graphql.dgs.client.codegen.BaseSubProjectionNode;
20+
21+
public class AuthorProjection<PARENT extends BaseSubProjectionNode<?, ?>, ROOT extends BaseSubProjectionNode<?, ?>> extends BaseSubProjectionNode<PARENT, ROOT> {
22+
public AuthorProjection(PARENT parent, ROOT root) {
23+
super(parent, root, java.util.Optional.of("Author"));
24+
}
25+
26+
public AuthorProjection<PARENT, ROOT> __typename() {
27+
getFields().put("__typename", null);
28+
return this;
29+
}
30+
31+
public AuthorProjection<PARENT, ROOT> id() {
32+
getFields().put("id", null);
33+
return this;
34+
}
35+
36+
public AuthorProjection<PARENT, ROOT> firstName() {
37+
getFields().put("firstName", null);
38+
return this;
39+
}
40+
41+
public AuthorProjection<PARENT, ROOT> lastName() {
42+
getFields().put("lastName", null);
43+
return this;
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
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 org.springframework.graphql.docs.client.dgsgraphqlclient;
18+
19+
import java.util.ArrayList;
20+
import java.util.HashMap;
21+
import java.util.HashSet;
22+
import java.util.List;
23+
import java.util.Map;
24+
import java.util.Set;
25+
26+
import com.netflix.graphql.dgs.client.codegen.GraphQLQuery;
27+
import graphql.language.VariableDefinition;
28+
29+
public class BookByIdGraphQLQuery extends GraphQLQuery {
30+
public BookByIdGraphQLQuery(String id, String queryName, Set<String> fieldsSet) {
31+
super("query", queryName);
32+
if (id != null || fieldsSet.contains("id")) {
33+
getInput().put("id", id);
34+
}
35+
}
36+
37+
public BookByIdGraphQLQuery(String id, String queryName, Set<String> fieldsSet,
38+
Map<String, String> variableReferences, List<VariableDefinition> variableDefinitions) {
39+
super("query", queryName);
40+
if (id != null || fieldsSet.contains("id")) {
41+
getInput().put("id", id);
42+
}
43+
if (variableDefinitions != null) {
44+
getVariableDefinitions().addAll(variableDefinitions);
45+
}
46+
47+
if (variableReferences != null) {
48+
getVariableReferences().putAll(variableReferences);
49+
}
50+
}
51+
52+
public BookByIdGraphQLQuery() {
53+
super("query");
54+
}
55+
56+
@Override
57+
public String getOperationName() {
58+
return "bookById";
59+
}
60+
61+
public static Builder newRequest() {
62+
return new Builder();
63+
}
64+
65+
public static class Builder {
66+
private Set<String> fieldsSet = new HashSet<>();
67+
68+
private final Map<String, String> variableReferences = new HashMap<>();
69+
70+
private final List<VariableDefinition> variableDefinitions = new ArrayList<>();
71+
72+
private String id;
73+
74+
private String queryName;
75+
76+
public BookByIdGraphQLQuery build() {
77+
return new BookByIdGraphQLQuery(this.id, this.queryName, this.fieldsSet, this.variableReferences, this.variableDefinitions);
78+
79+
}
80+
81+
public Builder id(String id) {
82+
this.id = id;
83+
this.fieldsSet.add("id");
84+
return this;
85+
}
86+
87+
public Builder idReference(String variableRef) {
88+
this.variableReferences.put("id", variableRef);
89+
this.variableDefinitions.add(VariableDefinition.newVariableDefinition(variableRef, new graphql.language.TypeName("ID")).build());
90+
this.fieldsSet.add("id");
91+
return this;
92+
}
93+
94+
public Builder queryName(String queryName) {
95+
this.queryName = queryName;
96+
return this;
97+
}
98+
}
99+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
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 org.springframework.graphql.docs.client.dgsgraphqlclient;
18+
19+
import com.netflix.graphql.dgs.client.codegen.BaseSubProjectionNode;
20+
21+
public class BooksProjectionRoot<PARENT extends BaseSubProjectionNode<?, ?>, ROOT extends BaseSubProjectionNode<?, ?>> extends BaseSubProjectionNode<PARENT, ROOT> {
22+
public BooksProjectionRoot() {
23+
super(null, null, java.util.Optional.of("Book"));
24+
}
25+
26+
public BooksProjectionRoot<PARENT, ROOT> __typename() {
27+
getFields().put("__typename", null);
28+
return this;
29+
}
30+
31+
public AuthorProjection<BooksProjectionRoot<PARENT, ROOT>, BooksProjectionRoot<PARENT, ROOT>> author(
32+
) {
33+
AuthorProjection<BooksProjectionRoot<PARENT, ROOT>, BooksProjectionRoot<PARENT, ROOT>> projection = new AuthorProjection<>(this, this);
34+
getFields().put("author", projection);
35+
return projection;
36+
}
37+
38+
public BooksProjectionRoot<PARENT, ROOT> id() {
39+
getFields().put("id", null);
40+
return this;
41+
}
42+
43+
public BooksProjectionRoot<PARENT, ROOT> name() {
44+
getFields().put("name", null);
45+
return this;
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
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 org.springframework.graphql.docs.client.dgsgraphqlclient;
18+
19+
20+
import java.util.List;
21+
22+
import org.springframework.graphql.client.ClientGraphQlResponse;
23+
import org.springframework.graphql.client.DgsGraphQlClient;
24+
import org.springframework.graphql.client.HttpGraphQlClient;
25+
import org.springframework.web.reactive.function.client.WebClient;
26+
27+
public class DgsClientUsage {
28+
29+
public void sendSingleQuery() {
30+
// tag::sendSingleQuery[]
31+
HttpGraphQlClient client = HttpGraphQlClient.create(WebClient.create("https://example.org/graphql"));
32+
DgsGraphQlClient dgsClient = DgsGraphQlClient.create(client); // <1>
33+
34+
List<Book> books = dgsClient.request(BookByIdGraphQLQuery.newRequest().id("42").build()) // <2>
35+
.projection(new BooksProjectionRoot<>().id().name()) // <3>
36+
.retrieveSync("books")
37+
.toEntityList(Book.class);
38+
// end::sendSingleQuery[]
39+
}
40+
41+
public void sendManyQueries() {
42+
// tag::sendManyQueries[]
43+
HttpGraphQlClient client = HttpGraphQlClient.create(WebClient.create("https://example.org/graphql"));
44+
DgsGraphQlClient dgsClient = DgsGraphQlClient.create(client); // <1>
45+
46+
ClientGraphQlResponse response = dgsClient
47+
.request(BookByIdGraphQLQuery.newRequest().id("42").build()) // <2>
48+
.queryAlias("firstBook") // <3>
49+
.projection(new BooksProjectionRoot<>().id().name())
50+
.request(BookByIdGraphQLQuery.newRequest().id("53").build()) // <4>
51+
.queryAlias("secondBook")
52+
.projection(new BooksProjectionRoot<>().id().name())
53+
.executeSync(); // <5>
54+
55+
Book firstBook = response.field("firstBook").toEntity(Book.class); // <6>
56+
Book secondBook = response.field("secondBook").toEntity(Book.class);
57+
// end::sendManyQueries[]
58+
}
59+
60+
record Book(Long id, String name) {
61+
62+
}
63+
}

spring-graphql/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ dependencies {
8686
testImplementation 'com.fasterxml.jackson.core:jackson-databind'
8787
testImplementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
8888
testImplementation 'org.apache.tomcat.embed:tomcat-embed-el:10.0.21'
89+
testImplementation('com.netflix.graphql.dgs.codegen:graphql-dgs-codegen-shared-core') {
90+
exclude group: "com.apollographql.federation", module: "federation-graphql-java-support"
91+
exclude group: "com.graphql-java"
92+
}
8993
testImplementation 'com.apollographql.federation:federation-graphql-java-support'
9094

9195
testRuntimeOnly 'org.apache.logging.log4j:log4j-core'

0 commit comments

Comments
 (0)