From 2dc2e5ab1189ecba282102edce61312927ef28ec Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Tue, 19 Apr 2022 10:53:15 +0200 Subject: [PATCH] Auto-configure RSocketGraphQlClient This commit contributes a `RSocketGraphQlClient.Builder` component to the context, pre-configured with the `RSocketStrategies`, a customized `RSocketConnector` and the expected data MIME type. See gh-30453 --- ...RSocketGraphQlClientAutoConfiguration.java | 56 ++++++++++++++ ...etGraphQlClientAutoConfigurationTests.java | 76 +++++++++++++++++++ .../src/docs/asciidoc/web/spring-graphql.adoc | 11 +-- .../rsocket/RSocketGraphQlClientExample.java | 27 +++---- .../rsocket/RSocketGraphQlClientExample.kt | 43 +++-------- 5 files changed, 162 insertions(+), 51 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/rsocket/RSocketGraphQlClientAutoConfiguration.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/rsocket/RSocketGraphQlClientAutoConfigurationTests.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/rsocket/RSocketGraphQlClientAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/rsocket/RSocketGraphQlClientAutoConfiguration.java new file mode 100644 index 0000000000..ff905fc633 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/rsocket/RSocketGraphQlClientAutoConfiguration.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-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.boot.autoconfigure.graphql.rsocket; + +import graphql.GraphQL; +import io.rsocket.RSocket; +import io.rsocket.transport.netty.client.TcpClientTransport; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Scope; +import org.springframework.graphql.client.RSocketGraphQlClient; +import org.springframework.messaging.rsocket.RSocketRequester; +import org.springframework.util.MimeTypeUtils; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for {@link RSocketGraphQlClient}. + * This auto-configuration creates {@link RSocketGraphQlClient.Builder} prototype beans, + * as the builders are stateful and should not be reused to build client instances with + * different configurations. + * + * @author Brian Clozel + * @since 2.7.0 + */ +@AutoConfiguration(after = RSocketRequesterAutoConfiguration.class) +@ConditionalOnClass({ GraphQL.class, RSocketGraphQlClient.class, RSocketRequester.class, RSocket.class, + TcpClientTransport.class }) +public class RSocketGraphQlClientAutoConfiguration { + + @Bean + @Scope("prototype") + @ConditionalOnMissingBean + public RSocketGraphQlClient.Builder rsocketGraphQlClientBuilder( + RSocketRequester.Builder rsocketRequesterBuilder) { + return RSocketGraphQlClient.builder(rsocketRequesterBuilder.dataMimeType(MimeTypeUtils.APPLICATION_GRAPHQL)); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/rsocket/RSocketGraphQlClientAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/rsocket/RSocketGraphQlClientAutoConfigurationTests.java new file mode 100644 index 0000000000..f46a2d25d5 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/rsocket/RSocketGraphQlClientAutoConfigurationTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-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.boot.autoconfigure.graphql.rsocket; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration; +import org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.graphql.client.RSocketGraphQlClient; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link RSocketGraphQlClientAutoConfiguration}. + * + * @author Brian Clozel + */ +class RSocketGraphQlClientAutoConfigurationTests { + + private static final RSocketGraphQlClient.Builder builderInstance = RSocketGraphQlClient.builder(); + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(RSocketStrategiesAutoConfiguration.class, + RSocketRequesterAutoConfiguration.class, RSocketGraphQlClientAutoConfiguration.class)); + + @Test + void shouldCreateBuilder() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(RSocketGraphQlClient.Builder.class)); + } + + @Test + void shouldGetPrototypeScopedBean() { + this.contextRunner.run((context) -> { + RSocketGraphQlClient.Builder first = context.getBean(RSocketGraphQlClient.Builder.class); + RSocketGraphQlClient.Builder second = context.getBean(RSocketGraphQlClient.Builder.class); + assertThat(first).isNotEqualTo(second); + }); + } + + @Test + void shouldNotCreateBuilderIfAlreadyPresent() { + this.contextRunner.withUserConfiguration(CustomRSocketGraphQlClientBuilder.class).run((context) -> { + RSocketGraphQlClient.Builder builder = context.getBean(RSocketGraphQlClient.Builder.class); + assertThat(builder).isEqualTo(builderInstance); + }); + } + + @Configuration(proxyBeanMethods = false) + static class CustomRSocketGraphQlClientBuilder { + + @Bean + RSocketGraphQlClient.Builder myRSocketGraphQlClientBuilder() { + return builderInstance; + } + + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-graphql.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-graphql.adoc index 451ac23509..8062175b1f 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-graphql.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-graphql.adoc @@ -117,13 +117,14 @@ Spring Boot supports many configuration properties under the `spring.graphql.cor RSocket is also supported as a transport, on top of WebSocket or TCP. Once the <>, we can configure our GraphQL handler on a particular route using configprop:spring.graphql.rsocket.mapping[]. -For example, configuring that mapping as `"graphql"` means we can use the `RSocketGraphQlClient` as follows. +For example, configuring that mapping as `"graphql"` means we can use that as a route when sending requests with the `RSocketGraphQlClient`. -For RSocket over TCP: -include::code:RSocketGraphQlClientExample[tag=tcp] +Spring Boot auto-configures a `RSocketGraphQlClient.Builder` bean that you can inject in your components: -For RSocket over WebSocket: -include::code:RSocketGraphQlClientExample[tag=websocket] +include::code:RSocketGraphQlClientExample[tag=builder] + +And then send a request: +include::code:RSocketGraphQlClientExample[tag=request] [[web.graphql.exception-handling]] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/web/graphql/transports/rsocket/RSocketGraphQlClientExample.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/web/graphql/transports/rsocket/RSocketGraphQlClientExample.java index c656be28c1..9df5ff0c53 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/web/graphql/transports/rsocket/RSocketGraphQlClientExample.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/web/graphql/transports/rsocket/RSocketGraphQlClientExample.java @@ -16,32 +16,29 @@ package org.springframework.boot.docs.web.graphql.transports.rsocket; -import java.net.URI; import java.time.Duration; import reactor.core.publisher.Mono; import org.springframework.graphql.client.RSocketGraphQlClient; +import org.springframework.stereotype.Component; +// tag::builder[] +@Component public class RSocketGraphQlClientExample { - public void rsocketOverTcp() { - // tag::tcp[] - RSocketGraphQlClient client = RSocketGraphQlClient.builder().tcp("example.spring.io", 8181).route("graphql") - .build(); - Mono book = client.document("{ bookById(id: \"book-1\"){ id name pageCount author } }") - .retrieve("bookById").toEntity(Book.class); - // end::tcp[] - book.block(Duration.ofSeconds(5)); + private final RSocketGraphQlClient graphQlClient; + + public RSocketGraphQlClientExample(RSocketGraphQlClient.Builder builder) { + this.graphQlClient = builder.tcp("example.spring.io", 8181).route("graphql").build(); } + // end::builder[] - public void rsocketOverWebSocket() { - // tag::websocket[] - RSocketGraphQlClient client = RSocketGraphQlClient.builder() - .webSocket(URI.create("wss://example.spring.io/rsocket")).route("graphql").build(); - Mono book = client.document("{ bookById(id: \"book-1\"){ id name pageCount author } }") + public void rsocketOverTcp() { + // tag::request[] + Mono book = this.graphQlClient.document("{ bookById(id: \"book-1\"){ id name pageCount author } }") .retrieve("bookById").toEntity(Book.class); - // end::websocket[] + // end::request[] book.block(Duration.ofSeconds(5)); } diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/web/graphql/transports/rsocket/RSocketGraphQlClientExample.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/web/graphql/transports/rsocket/RSocketGraphQlClientExample.kt index 7a674b7fa3..79988fe089 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/web/graphql/transports/rsocket/RSocketGraphQlClientExample.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/web/graphql/transports/rsocket/RSocketGraphQlClientExample.kt @@ -17,19 +17,21 @@ package org.springframework.boot.docs.web.graphql.transports.rsocket import org.springframework.graphql.client.RSocketGraphQlClient -import java.net.URI +import org.springframework.stereotype.Component import java.time.Duration +// tag::builder[] +@Component +class RSocketGraphQlClientExample(private val builder: RSocketGraphQlClient.Builder<*>) { +// end::builder[] -class RSocketGraphQlClientExample { + val graphQlClient = builder.tcp("example.spring.io", 8181) + .route("graphql") + .build() fun rsocketOverTcp() { - // tag::tcp[] - val client = RSocketGraphQlClient.builder() - .tcp("example.spring.io", 8181) - .route("graphql") - .build() - val book = client.document( + // tag::request[] + val book = graphQlClient.document( """ { bookById(id: "book-1"){ @@ -39,31 +41,10 @@ class RSocketGraphQlClientExample { author } } - """) - .retrieve("bookById").toEntity(Book::class.java) - // end::tcp[] - book.block(Duration.ofSeconds(5)) - } - - fun rsocketOverWebSocket() { - // tag::websocket[] - val client = RSocketGraphQlClient.builder() - .webSocket(URI.create("wss://example.spring.io/rsocket")) - .route("graphql") - .build() - val book = client.document( """ - { - bookById(id: "book-1"){ - id - name - pageCount - author - } - } - """) + ) .retrieve("bookById").toEntity(Book::class.java) - // end::websocket[] + // end::request[] book.block(Duration.ofSeconds(5)) }