Vert.x Web GraphQL

Vert.x Web GraphQL 用 GraphQL-Java 库扩展了 Vert.x Web,您可以用它创建一个 GraphQL 的服务器。

提示
这是一个 Vert.x Web GraphQL 的参考文档。 强烈建议您先熟悉一下 GraphQL-Java API。 您可以从此处开始阅读 GraphQL-Java documentation

由此开始

若要使用该模块,请在您的 Maven POM 文件的 dependencies 部分中添加以下依赖:

<dependency>
 <groupId>io.vertx</groupId>
 <artifactId>vertx-web-graphql</artifactId>
 <version>4.1.8</version>
</dependency>

或您使用的是Gradle:

compile 'io.vertx:vertx-web-graphql:4.1.8'

设置处理器

HTTP

为此您需要创建 Vert.x Web Route 以及 GraphQLHandler

GraphQL graphQL = setupGraphQLJava();

router.route("/graphql").handler(GraphQLHandler.create(graphQL));

该处理器可处理 GET 以及 POST 请求。 当然,您也可以限制处理器仅处理其中一种 HTTP 方法:

GraphQL graphQL = setupGraphQLJava();

router.post("/graphql").handler(GraphQLHandler.create(graphQL));
重要
GraphQLHandler 需要 BodyHandler ,用于读取 POST 请求体内容。

查询批处理

查询批处理将数组(而非单个对象)通过 post 方法发送到 GraphQL 端点(endpoint)。

Vert.x Web GraphQL 可以处理此类请求,但该特性默认是被禁用的。 如要启用,需创建带参数的 GraphQLHandler

GraphQLHandlerOptions options = new GraphQLHandlerOptions()
  .setRequestBatchingEnabled(true);

GraphQLHandler handler = GraphQLHandler.create(graphQL, options);

GraphiQL IDE

在构建应用程序时,可以在 GraphiQL 中很方便地测试您的 GraphQL 查询。

为此,您需要为 GraphiQL 资源创建一个路由(route)及相应的处理器 GraphiQLHandler

GraphiQLHandlerOptions options = new GraphiQLHandlerOptions()
  .setEnabled(true);

router.route("/graphiql/*").handler(GraphiQLHandler.create(options));

随后浏览以下地址 http://localhost:8080/graphiql/

注意
出于安全考虑,默认情况下 GraphiQL 用户界面是禁用的。 所以需要配置 GraphiQLHandlerOptions 才能启用 GraphiQL 用户界面。
提示
当 Vert.x Web 在开发模式下运行时,将自动启用 GraphiQL。 要启用开发模式,请将 VERTXWEB_ENVIRONMENT 环境变量或 vertxweb.environment 系统属性设置为 dev 。 在这种情况下,创建 GraphiQLHandler 时,无需设置 GraphiQLHandlerOptionsenabled 属性。

如果您的应用程序受身份验证保护,则可以动态自定义 GraphiQL 发送的标头(headers):

graphiQLHandler.graphiQLRequestHeaders(rc -> {
  String token = rc.get("token");
  return MultiMap.caseInsensitiveMultiMap().add("Authorization", "Bearer " + token);
});

router.route("/graphiql/*").handler(graphiQLHandler);

请参考 GraphiQLHandlerOptions 文档以获取更多详细信息。

您可以使用 Apollo WebSocketLink 连接 websocket。 这对订阅 GraphQL schema 特别有帮助,但您亦可以使用 websocket 查询和变更。(译者注:GraphQL的三种操作类型的翻译及原文:查询 Query,变更 Mutation以及订阅 Subscription)

GraphQL graphQL = setupGraphQLJava();

router.route("/graphql").handler(ApolloWSHandler.create(graphQL));
重要
想要支持 graphql-ws websocket 子协议,必需在服务器配置中添加:
HttpServerOptions httpServerOptions = new HttpServerOptions()
  .addWebSocketSubProtocol("graphql-ws");
vertx.createHttpServer(httpServerOptions)
  .requestHandler(router)
  .listen(8080);
注意
如果要在同一路径中支持 WebSocketLink 和 HttpLink ,您可先添加 ApolloWSHandler ,然后再添加 GraphQLHandler 。
GraphQL graphQL = setupGraphQLJava();

router.route("/graphql").handler(ApolloWSHandler.create(graphQL));
router.route("/graphql").handler(GraphQLHandler.create(graphQL));

您可在此处找到配置 Apollo 订阅客户端(SubscriptionClient)的方法: https://github.com/apollographql/subscriptions-transport-ws

重要
一个 DataFetcher 订阅必需返回一个 org.reactivestreams.Publisher 实例。

获取数据

GraphQL-Java API 非常适合异步环境:查询操作默认策略便是异步执行(对于变更,则是串行异步)。

避免阻塞 eventloop ,您需要做的是实现 data fetchers 并返回 CompletionStage ,而不是直接返回结果。(译者注: Data Fetcher 下文统一保留原文、不翻译)

DataFetcher<CompletionStage<List<Link>>> dataFetcher = environment -> {

  CompletableFuture<List<Link>> completableFuture = new CompletableFuture<>();

  retrieveLinksFromBackend(environment, ar -> {
    if (ar.succeeded()) {
      completableFuture.complete(ar.result());
    } else {
      completableFuture.completeExceptionally(ar.cause());
    }
  });

  return completableFuture;
};

RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
  .type("Query", builder -> builder.dataFetcher("allLinks", dataFetcher))
  .build();

使用基于回调的API

实现一个返回 CompletionStage 的 DataFetcher 并不复杂。 但当您使用基于回调的 Vert.x API 时,需要一些八股文代码。

这时可以参考 VertxDataFetcher 获取帮助:

VertxDataFetcher<List<Link>> dataFetcher = VertxDataFetcher.create((env, promise) -> {
  retrieveLinksFromBackend(env, promise);
});

RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
  .type("Query", builder -> builder.dataFetcher("allLinks", dataFetcher))
  .build();

使用 Vert.x Future

VertxDataFetcher 对于使用 Future 化的 API 也有帮助:

VertxDataFetcher<List<Link>> dataFetcher = VertxDataFetcher.create(environment -> {
  Future<List<Link>> future = retrieveLinksFromBackend(environment);
  return future;
});

RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
  .type("Query", builder -> builder.dataFetcher("allLinks", dataFetcher))
  .build();

为数据获取器提供上下文

GraphQLHandler 通常是最后一个声明的处理器。 例如,您可以通过身份验证保护您的应用程序。

在这种情况下,您的 DataFetcher 可能需要知道哪个用户已登录,才能缩小结果范围。 假设您的身份验证层在 RoutingContext 中存储了一个 User 对象。

您可以通过 DataFetchingEnvironment 来获取这个 User 对象:

VertxDataFetcher<List<Link>> dataFetcher = VertxDataFetcher.create((environment, promise) -> {

  RoutingContext routingContext = environment.getContext();

  User user = routingContext.get("user");

  retrieveLinksPostedBy(user, promise);

});
注意
RoutingContext 可用于任何类型的 DataFetcher ,而不仅仅是 VertxDataFetcher

如果您不想将 RoutingContext 暴露给 DataFetcher ,请配置 GraphQL 处理器并自定义上下文对象:

VertxDataFetcher<List<Link>> dataFetcher = VertxDataFetcher.create((environment, promise) -> {

  // 以 User 对象作为自定义上下文对象
  User user = environment.getContext();

  retrieveLinksPostedBy(user, promise);

});

GraphQL graphQL = setupGraphQLJava(dataFetcher);

// 设置处理器时,自定义查询上下文对象
GraphQLHandler handler = GraphQLHandler.create(graphQL).queryContext(routingContext -> {

  return routingContext.get("user");

});

router.route("/graphql").handler(handler);

JSON 类型数据结果

默认的GraphQL DataFetcher 是 PropertyDataFetcher 。 因此,它无需进一步配置即可读取域对象的字段。

不过,某些 Vert.x 数据客户端会返回 JsonArrayJsonObject 类型的结果。

如果您不需要(或不想)使用域对象层,则可以将 GraphQL-Java 配置改为使用 VertxPropertyDataFetcher

RuntimeWiring.Builder builder = RuntimeWiring.newRuntimeWiring();

builder.wiringFactory(new WiringFactory() {

  @Override
  public DataFetcher<Object> getDefaultDataFetcher(FieldWiringEnvironment environment) {

    return VertxPropertyDataFetcher.create(environment.getFieldDefinition().getName());

  }
});
提示
VertxPropertyDataFetcher 包装了一个 PropertyDataFetcher ,因此您也可以通过它使用域对象。

批量加载

DataLoader 可以批量化请求并缓存结果,助您高效地加载数据。(译者注:与 DataFetcher 对应,DataLoader保留原文不翻译)

首先,创建一个批量加载器:

BatchLoaderWithContext<String, Link> linksBatchLoader = (ids, env) -> {
  // retrieveLinksFromBackend takes a list of ids and returns a CompletionStage for a list of links
  return retrieveLinksFromBackend(ids, env);
};

然后,配置 GraphQLHandler ,为每个请求创建一个 DataLoaderRegistry

GraphQLHandler handler = GraphQLHandler.create(graphQL).dataLoaderRegistry(rc -> {

  DataLoader<String, Link> linkDataLoader = DataLoader.newDataLoader(linksBatchLoader);

  return new DataLoaderRegistry().register("link", linkDataLoader);

});

如果您使用了 Vert.x 的 API,那么您可以使用 VertxBatchLoaderVertxMappedBatchLoader 来简化代码:

BatchLoaderWithContext<Long, String> commentsBatchLoader = VertxBatchLoader.create((ids, env) -> {
  // findComments takes a list of ids and returns a Future for a list of links
  return findComments(ids, env);
});

文件上传

GraphQL multipart 请求 是用于 GraphQL 请求的可互操作表单结构。 通过启用此功能,GraphQL 客户端将能够使用单个变更(Mutation)调用来上传文件。 所有服务器端文件处理逻辑都抽象到了 GraphQLHandler

启用该功能需要创建一个 GraphQLHandler ,并将 requestMultipartEnabled 配置设置为true,然后添加 BodyHandler 到路由。

GraphQLHandler graphQLHandler = GraphQLHandler.create(
  setupGraphQLJava(),
  new GraphQLHandlerOptions().setRequestMultipartEnabled(true)
);

Router router = Router.router(vertx);

router.route().handler(BodyHandler.create());
router.route("/graphql").handler(graphQLHandler);
重要
如果路由没有 BodyHandler ,则 multipart 请求解析器无法处理 GraphQL 的变更请求。

最后,创建 Upload scalar 并设置到 RuntimeWiring 中:

RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring().scalar(UploadScalar.build()).build();

FileUpload 实例可以通过 DataFetchingEnvironment::getArgument 方法获取。

FileUpload file = environment.getArgument("myFile");

RxJava 3 API

配置 Rxified 路由

若需要在 Rxified 路由 中处理 GraphQL 请求,则请确保已导入了 GraphQLHandler 类。

使用 Vert.x 的 Rxified API

GraphQL-Java 期望 DataFetcher 和批量加载器使用 CompletionStage 提供异步结果。

但是若您使用 Vert.x Rxified API (如 Web 客户端Cassandra 客户端),则需要适配 SingleMaybe 对象。

RxJavaJdk8Interop 库提供了这些适配工具。 您可以在 Maven 构建配置文件的 dependencies 部分中增加以下依赖:

<dependency>
 <groupId>com.github.akarnokd</groupId>
 <artifactId>rxjava3-jdk8-interop</artifactId>
 <version>3.0.0-RC6</version>
</dependency>

如果您使用 Gradle,则增加以下依赖配置:

implementation 'com.github.akarnokd:rxjava3-jdk8-interop:3.0.0-RC6'

然后可以从 Single 结果中创建 DataFetcher :

Single<String> data = loadDataFromBackend();
DataFetcher<CompletionStage<String>> fetcher = environment -> {
 return data.to(SingleInterop.get());
};

同样,可以使用 MaybeInterop 处理 Maybe 结果。