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:
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user