Make transports private and drop builders

Now that transport specific GraphQlClient extensions provide a unified
builder for GraphQlClient and transport configuration, the transports
become a private implementation detail.

See gh-10
This commit is contained in:
rstoyanchev
2022-03-02 14:01:22 +00:00
parent 5a73e05040
commit 347f51941a
5 changed files with 12 additions and 282 deletions

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.graphql.client;
import graphql.ExecutionResult;
@@ -21,8 +22,9 @@ import reactor.core.publisher.Mono;
import org.springframework.graphql.GraphQlRequest;
/**
* Contract for a transport, over which to execute GraphQL requests.
* Contract for GraphQL request execution over some transport.
*
* @author Rossen Stoyanchev
* @since 1.0.0

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.graphql.client;
import java.util.Map;
@@ -33,24 +34,10 @@ import org.springframework.web.reactive.function.client.WebClient;
* Supports only single-response requests over HTTP POST. For subscription
* requests, see {@link WebSocketGraphQlTransport}.
*
* <p>Use the builder to initialize the transport and the {@code GraphQlClient}
* in a single chain:
*
* <pre style="class">
* GraphQlClient client = HttpGraphQlTransport.builder(webClient).buildClient();
* </pre>
*
* <p>Or build the transport and the client separately:
*
* <pre style="class">
* HttpGraphQlTransport transport = HttpGraphQlTransport.create(webClient);
* GraphQlClient client = GraphQlClient.create(transport);
* </pre>
*
* @author Rossen Stoyanchev
* @since 1.0.0
*/
public class HttpGraphQlTransport implements GraphQlTransport {
final class HttpGraphQlTransport implements GraphQlTransport {
private static final ParameterizedTypeReference<Map<String, Object>> MAP_TYPE =
new ParameterizedTypeReference<Map<String, Object>>() {};
@@ -59,20 +46,12 @@ public class HttpGraphQlTransport implements GraphQlTransport {
private final WebClient webClient;
private HttpGraphQlTransport(WebClient webClient) {
HttpGraphQlTransport(WebClient webClient) {
Assert.notNull(webClient, "WebClient is required");
this.webClient = webClient;
}
/**
* Return the underlying {@code WebClient}.
*/
public WebClient getWebClient() {
return this.webClient;
}
@Override
public Mono<ExecutionResult> execute(GraphQlRequest request) {
return this.webClient.post()
@@ -89,66 +68,4 @@ public class HttpGraphQlTransport implements GraphQlTransport {
throw new UnsupportedOperationException("Subscriptions not supported over HTTP");
}
/**
* Static factory method with a {@code WebClient} to use.
*/
public static HttpGraphQlTransport create(WebClient webClient) {
return new HttpGraphQlTransport(webClient);
}
/**
* Static method to obtain a {@code Builder}.
*/
public static Builder builder(WebClient webClient) {
return new Builder(webClient);
}
/**
* Builder for {@link HttpGraphQlTransport} or a {@link GraphQlClient}
* configured with the transport.
*/
public static class Builder {
private WebClient webClient;
private Builder(WebClient webClient) {
this.webClient = webClient;
}
/**
* Set the {@code WebClient} to use.
*/
public Builder webClient(WebClient webClient) {
this.webClient = webClient;
return this;
}
/**
* Build the {@code HttpGraphQlTransport} instance.
*/
public HttpGraphQlTransport build() {
return new HttpGraphQlTransport(this.webClient);
}
/**
* Continue on to build a {@link GraphQlClient} configured with the
* transport configured here so far.
*/
public GraphQlClient.Builder configureClient() {
return GraphQlClient.builder(build());
}
/**
* Shortcut to build a {@link GraphQlClient} configured with the
* transport configured here.
*/
public GraphQlClient buildClient() {
return GraphQlClient.builder(build()).build();
}
}
}

View File

@@ -34,6 +34,7 @@ final class TypeRefAdapter<T> extends TypeRef<T> {
private final Type type;
TypeRefAdapter(Class<T> clazz) {
this.type = clazz;
}

View File

@@ -16,7 +16,6 @@
package org.springframework.graphql.client;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -24,7 +23,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Supplier;
import graphql.ExecutionResult;
import graphql.GraphQLError;
@@ -40,7 +38,6 @@ import org.springframework.graphql.support.MapExecutionResult;
import org.springframework.graphql.support.MapGraphQlError;
import org.springframework.graphql.web.webflux.GraphQlWebSocketMessage;
import org.springframework.http.HttpHeaders;
import org.springframework.http.codec.ClientCodecConfigurer;
import org.springframework.http.codec.CodecConfigurer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -52,46 +49,11 @@ import org.springframework.web.reactive.socket.client.WebSocketClient;
/**
* {@link GraphQlTransport} for GraphQL over WebSocket via {@link WebSocketClient}.
*
* <p>Use the builder to initialize the transport and the {@link GraphQlClient}
* in a single chain:
*
* <pre style="class">
* GraphQlClient client =
* WebSocketGraphQlTransport.builder(url, webSocketClient)
* .headers(headers -> ... )
* .buildClient();
* </pre>
*
* <p>Or build the transport and the client separately:
*
* <pre style="class">
* WebSocketGraphQlTransport transport =
* WebSocketGraphQlTransport.builder(url, webSocketClient)
* .headers(headers -> ... )
* .build();
*
* GraphQlClient client = GraphQlClient.create(transport);
* </pre>
*
* <p>Once the client is built, you can obtain the underlying transport, mutate
* it, and rebuild transport and client as follows:
*
* <pre style="class">
* WebSocketGraphQlTransport transport = client.getTransport(WebSocketGraphQlTransport.class);
*
* if (transport != null) {
* GraphQlClient newClient = transport.mutate()
* .headers(headers -> ... )
* .buildClient();
* }
* </pre>
*
*
* @author Rossen Stoyanchev
* @since 1.0.0
* @see <a href="https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md">GraphQL over WebSocket protocol</a>
*/
public final class WebSocketGraphQlTransport implements GraphQlTransport {
final class WebSocketGraphQlTransport implements GraphQlTransport {
private static final Log logger = LogFactory.getLog(WebSocketGraphQlTransport.class);
@@ -100,21 +62,16 @@ public final class WebSocketGraphQlTransport implements GraphQlTransport {
private final Mono<GraphQlSession> graphQlSessionMono;
private final Supplier<Builder> mutateBuilder;
private WebSocketGraphQlTransport(
WebSocketGraphQlTransport(
URI uri, HttpHeaders headers, WebSocketClient client, CodecConfigurer codecConfigurer,
@Nullable Object connectionInitPayload, Consumer<Map<String, Object>> connectionAckHandler,
Supplier<Builder> mutateBuilder) {
@Nullable Object connectionInitPayload, Consumer<Map<String, Object>> connectionAckHandler) {
this.graphQlSessionHandler = new GraphQlSessionHandler(
codecConfigurer, connectionInitPayload, connectionAckHandler);
this.graphQlSessionMono = initGraphQlSession(uri, headers, client, this.graphQlSessionHandler)
.cacheInvalidateWhen(GraphQlSession::notifyWhenClosed);
this.mutateBuilder = mutateBuilder;
}
private static Mono<GraphQlSession> initGraphQlSession(
@@ -167,154 +124,6 @@ public final class WebSocketGraphQlTransport implements GraphQlTransport {
return this.graphQlSessionMono.flatMapMany(session -> session.executeSubscription(request));
}
/**
* Create a builder initialized from the configuration of "this" transport.
* Use this to build a new instance configured differently.
*/
public Builder mutate() {
return this.mutateBuilder.get();
}
/**
* Static factory method with the client and the endpoint to connect to.
* @param uri the WebSocket handshake URL
* @param client the WebSocket client
* @return the created instance
*/
public static WebSocketGraphQlTransport create(URI uri, WebSocketClient client) {
return new Builder(uri, client).build();
}
/**
* Return a builder with further options for the transport.
* @param uri the WebSocket handshake URL
* @param client the WebSocket client
* @return the builder instance
*/
public static Builder builder(URI uri, WebSocketClient client) {
return new Builder(uri, client);
}
/**
* Builder for {@link WebSocketGraphQlTransport} with an option to build a
* {@link GraphQlClient} instead, configured with the transport.
*/
public static final class Builder {
private URI url;
private final HttpHeaders headers = new HttpHeaders();
private WebSocketClient client;
private CodecConfigurer codecsConfigurer = ClientCodecConfigurer.create();
@Nullable
private Object initPayload;
private Consumer<Map<String, Object>> connectionAckHandler = ackPayload -> {};
Builder(URI url, WebSocketClient client) {
this.url = url;
this.client = client;
}
/**
* Set the URL for the WebSocket handshake request.
*/
public Builder url(URI uri) {
Assert.notNull(uri, "URI is required");
this.url = uri;
return this;
}
/**
* Add an HTTP header for the WebSocket handshake request.
*/
public Builder header(String name, String... values) {
Arrays.stream(values).forEach(value -> this.headers.add(name, value));
return this;
}
/**
* Provides access to every header declared so far with the possibility
* to add, replace, or remove.
*/
public Builder headers(Consumer<HttpHeaders> headersConsumer) {
headersConsumer.accept(this.headers);
return this;
}
/**
* Set the {@code WebSocketClient} to connect over.
*/
public Builder webSocketClient(WebSocketClient client) {
Assert.notNull(client, "WebSocketClient is required");
this.client = client;
return this;
}
/**
* Provide a {@code CodecConfigurer} that should contain encoders and
* decoders for JSON to be able to encode and decode GraphQL messages.
*/
public Builder codecConfigurer(CodecConfigurer codecConfigurer) {
this.codecsConfigurer = codecConfigurer;
return this;
}
/**
* The payload to send with the "connection_init" message.
*/
public Builder connectionInitPayload(@Nullable Object connectionInitPayload) {
this.initPayload = connectionInitPayload;
return this;
}
/**
* Handler for the payload received with the "connection_ack" message.
*/
public Builder connectionAckHandler(Consumer<Map<String, Object>> ackHandler) {
this.connectionAckHandler = ackHandler;
return this;
}
/**
* Build the transport instance.
*/
public WebSocketGraphQlTransport build() {
Supplier<Builder> mutateBuilder = () -> new Builder(this.url, this.client)
.headers(theHeaders -> theHeaders.putAll(this.headers))
.codecConfigurer(codecsConfigurer.clone())
.connectionInitPayload(this.initPayload)
.connectionAckHandler(this.connectionAckHandler);
return new WebSocketGraphQlTransport(this.url, this.headers, this.client,
this.codecsConfigurer, this.initPayload, this.connectionAckHandler, mutateBuilder);
}
/**
* Proceed to build a client that is configured with the transport from
* this builder.
*/
public GraphQlClient.Builder configureClient() {
return GraphQlClient.builder(build());
}
/**
* Build a client configured with the transport from this builder.
*/
public GraphQlClient buildClient() {
return GraphQlClient.builder(build()).build();
}
}
/**
* Client {@code WebSocketHandler} for GraphQL that deals with WebSocket

View File

@@ -28,7 +28,8 @@ public interface DocumentSource {
/**
* Return the document that matches the given name.
* @param name the name to use for the lookup
* @return {@code Mono} that provides the document or returns an error
* @return {@code Mono} that completes either with the document content or
* with an error, but never empty.
*/
Mono<String> getDocument(String name);