Add Java DSL for GraphQL (#3934)

* Add Java DSL for GraphQL

* GraphQl.outboundChannelAdapter() -> outboundGateway()

* * Add `private` ctor to `GraphQl`

* * Just `GraphQl.gateway()` - there is not going to be any other components
to handle GraphQL operations.
Also, the `outbound` in the name might confuse, where we, essentially, query a GraphQL data

* Fix typo in the doc

Co-authored-by: Gary Russell <grussell@vmware.com>

Co-authored-by: Gary Russell <grussell@vmware.com>
This commit is contained in:
Artem Bilan
2022-11-02 10:19:58 -04:00
committed by GitHub
parent a4ac531c4a
commit e3e55c3a85
5 changed files with 174 additions and 36 deletions

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.graphql.dsl;
import org.springframework.graphql.ExecutionGraphQlService;
/**
* Factory class for GraphQL components DSL.
*
* @author Artem Bilan
*
* @since 6.0
*/
public final class GraphQl {
/**
* Create an instance of {@link GraphQlMessageHandlerSpec} for the provided {@link ExecutionGraphQlService}.
* @param graphQlService the {@link ExecutionGraphQlService} to use.
* @return the spec.
*/
public static GraphQlMessageHandlerSpec gateway(ExecutionGraphQlService graphQlService) {
return new GraphQlMessageHandlerSpec(graphQlService);
}
private GraphQl() {
}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.graphql.dsl;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;
import org.springframework.expression.Expression;
import org.springframework.graphql.ExecutionGraphQlService;
import org.springframework.integration.dsl.MessageHandlerSpec;
import org.springframework.integration.expression.FunctionExpression;
import org.springframework.integration.graphql.outbound.GraphQlMessageHandler;
import org.springframework.messaging.Message;
/**
* The {@link MessageHandlerSpec} for {@link GraphQlMessageHandler}.
*
* @author Artem Bilan
*
* @since 6.0
*/
public class GraphQlMessageHandlerSpec extends MessageHandlerSpec<GraphQlMessageHandlerSpec, GraphQlMessageHandler> {
protected GraphQlMessageHandlerSpec(ExecutionGraphQlService graphQlService) {
this.target = new GraphQlMessageHandler(graphQlService);
}
public GraphQlMessageHandlerSpec operation(String operation) {
this.target.setOperation(operation);
return this;
}
public GraphQlMessageHandlerSpec operation(Function<Message<?>, String> operationFunction) {
return operationExpression(new FunctionExpression<>(operationFunction));
}
public GraphQlMessageHandlerSpec operationExpression(String operationExpression) {
return operationExpression(PARSER.parseExpression(operationExpression));
}
public GraphQlMessageHandlerSpec operationExpression(Expression operationExpression) {
this.target.setOperationExpression(operationExpression);
return this;
}
public GraphQlMessageHandlerSpec operationName(String operationName) {
this.target.setOperationName(operationName);
return this;
}
public GraphQlMessageHandlerSpec operationName(Function<Message<?>, String> operationNameFunction) {
return operationNameExpression(new FunctionExpression<>(operationNameFunction));
}
public GraphQlMessageHandlerSpec operationNameExpression(String operationNameExpression) {
return operationNameExpression(PARSER.parseExpression(operationNameExpression));
}
public GraphQlMessageHandlerSpec operationNameExpression(Expression operationNameExpression) {
this.target.setOperationNameExpression(operationNameExpression);
return this;
}
public GraphQlMessageHandlerSpec variables(Function<Message<?>, Map<String, Object>> variablesFunction) {
return variablesExpression(new FunctionExpression<>(variablesFunction));
}
public GraphQlMessageHandlerSpec variablesExpression(String variablesExpression) {
return variablesExpression(PARSER.parseExpression(variablesExpression));
}
public GraphQlMessageHandlerSpec variablesExpression(Expression variablesExpression) {
this.target.setVariablesExpression(variablesExpression);
return this;
}
public GraphQlMessageHandlerSpec locale(Locale locale) {
this.target.setLocale(locale);
return this;
}
public GraphQlMessageHandlerSpec executionId(Function<Message<?>, String> executionIdExpression) {
return executionIdExpression(new FunctionExpression<>(executionIdExpression));
}
public GraphQlMessageHandlerSpec executionIdExpression(String executionIdExpression) {
return executionIdExpression(PARSER.parseExpression(executionIdExpression));
}
public GraphQlMessageHandlerSpec executionIdExpression(Expression executionIdExpression) {
this.target.setExecutionIdExpression(executionIdExpression);
return this;
}
}

View File

@@ -61,7 +61,7 @@ public class GraphQlMessageHandler extends AbstractReplyProducingMessageHandler
private Expression executionIdExpression =
new FunctionExpression<Message<?>>(message -> message.getHeaders().getId());
public GraphQlMessageHandler(final ExecutionGraphQlService graphQlService) {
public GraphQlMessageHandler(ExecutionGraphQlService graphQlService) {
Assert.notNull(graphQlService, "'graphQlService' must not be null");
this.graphQlService = graphQlService;
setAsync(true);

View File

@@ -45,6 +45,7 @@ import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.MessageChannels;
import org.springframework.integration.graphql.dsl.GraphQl;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.PollableChannel;
@@ -121,8 +122,8 @@ public class GraphQlMessageHandlerTests {
Locale locale = Locale.getDefault();
this.graphQlMessageHandler.setLocale(locale);
Mono<ExecutionGraphQlResponse> resultMono =
(Mono<ExecutionGraphQlResponse>) this.graphQlMessageHandler.handleRequestMessage(new GenericMessage<>(fakeQuery));
var resultMono = (Mono<ExecutionGraphQlResponse>) this.graphQlMessageHandler.handleRequestMessage(
new GenericMessage<>(fakeQuery));
StepVerifier.create(resultMono)
.consumeNextWith(result -> {
assertThat(result).isInstanceOf(ExecutionGraphQlResponse.class);
@@ -243,18 +244,9 @@ public class GraphQlMessageHandlerTests {
@SubscriptionMapping
public Flux<QueryResult> results() {
return Flux.just(
new QueryResult("test-data-01"),
new QueryResult("test-data-02"),
new QueryResult("test-data-03"),
new QueryResult("test-data-04"),
new QueryResult("test-data-05"),
new QueryResult("test-data-06"),
new QueryResult("test-data-07"),
new QueryResult("test-data-08"),
new QueryResult("test-data-09"),
new QueryResult("test-data-10")
);
return Flux.range(1, 10)
.map(d -> String.format("test-data-%02d", d))
.map(QueryResult::new);
}
}
@@ -280,14 +272,9 @@ public class GraphQlMessageHandlerTests {
static class TestConfig {
@Bean
GraphQlMessageHandler handler(ExecutionGraphQlService graphQlService) {
return new GraphQlMessageHandler(graphQlService);
}
@Bean
IntegrationFlow graphqlQueryMessageHandlerFlow(GraphQlMessageHandler handler) {
IntegrationFlow graphqlQueryMessageHandlerFlow(ExecutionGraphQlService graphQlService) {
return IntegrationFlow.from(MessageChannels.flux("inputChannel"))
.handle(handler)
.handle(GraphQl.gateway(graphQlService))
.channel(c -> c.flux("resultChannel"))
.get();
}

View File

@@ -44,19 +44,18 @@ See documentation for the `ExecutionGraphQlResponse` how to process the GraphQL
[source, java]
----
@Bean
GraphQlMessageHandler handler(ExecutionGraphQlService graphQlService) {
GraphQlMessageHandler graphQlMessageHandler = new GraphQlMessageHandler(graphQlService);
graphQlMessageHandler.setOperation("""
query HeroNameAndFriends($episode: Episode) {
hero(episode: $episode) {
name
friends {
name
}
}
}""");
graphQlMessageHandler.setVariablesExpression(new SpelExpressionParser().parseExpression("{episode:'JEDI'}"));
return graphQlMessageHandler;
GraphQlMessageHandlerSpec graphQlMessageHandlerSpec(ExecutionGraphQlService graphQlService) {
return GraphQl.gateway(graphQlService)
.operation("""
query HeroNameAndFriends($episode: Episode) {
hero(episode: $episode) {
name
friends {
name
}
}
}""")
.variablesExpression("{episode:'JEDI'}");
}
@Bean
@@ -102,4 +101,4 @@ public SubscriptionPublisher obtainSubscriptionResult(RequestOutput output) {
====
Such an outbound gateway can be used not only for GraphQL request via HTTP, but from any upstream endpoint which produces or carries a GraphQL operation or its arguments in the message.
The result of the `GraphQlMessageHandler` handling can be produces as a reply to the upstream request or sent downstream for further processing in the integration flow.
The result of the `GraphQlMessageHandler` handling can be produced as a reply to the upstream request or sent downstream for further processing in the integration flow.