使用 graphql-java 构建 Relay GraphQL 服务器

Posted

技术标签:

【中文标题】使用 graphql-java 构建 Relay GraphQL 服务器【英文标题】:Building a Relay GraphQL server with graphql-java 【发布时间】:2020-12-02 06:51:57 【问题描述】:

我们正在使用 graphql-java 构建一个 graphql 服务器,但在符合 Relay 规范时遇到了问题。根据 Relay 规范,所有节点都必须可以通过单个查询检索:node(id: ID)。 graphql-java 文档显示了对 Relay 节点接口的支持,但是实际实现这一点的文档相当稀少。我们遇到的确切问题是了解如何为节点查询生成通用查找? Relay Todo 示例:https://github.com/graphql-java/todomvc-relay-java 展示了一种极其简单的基于代码的方法,使用单个数据提取器,此处的示例永远不需要“读取”节点“类型”或将该请求委托给正确的数据提取器。

schema.graphqls:

type Query 
    node(id: ID!): Node
    user(id: ID!): User
    otherType(id:ID!): OtherType


interface Node 
   id: ID!


type User implements Node 
   id: ID!
   firstName: String
   lastName: String


type OtherType implements Node 
   id: ID!
   description: String
   stuff: String

我们目前正在使用 SDL 来生成我们的架构(如下所示)

@Bean
private GraphQLSchema schema() 
     Url url = Resources.getResource("schema.graphqls");
     String sdl = Resources.toString(url, Charsets.UTF_8);
     GraphQLSchema graphQLSchema = buildSchema(sdl);
     this.graphQL = GraphQL.newGraphQL(graphQLSchema).build();
     return graphQLSchema;

private GraphQLSchema buildSchema(String sdl) 
      TypeDefinitionRegistry typeRegistry - new SchemaParser().parse(sdl);
      RuntimeWiring runtimeWiring = buildWiring();
      SchemaGenerator schemaGenerator = new SchemaGenerator();
      return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);

private RuntimeWiring buildWiring()
    return RuntimeWiring.newRuntimeWiring()
    .type(newTypeWiring("Query")
          .dataFetcher("user", userDispatcher.getUserById()))
    .type(newTypeWiring("Query")
          .dataFetcher("otherType", otherTypeDispatcher.getOtherTypeById()))
    .type(newTypeWiring("Query")
          .dataFetcher("node", /*How do we implement this data fetcher? */))
    .build()

假设我们必须连接一个数据提取器,如上面最后一行所示。然而,这是文档开始变得模糊的地方。以上是否符合 Relay 规范?我们将如何实现一个单节点 dataFetcher,它返回对任何单个节点的引用,而不管底层类型(User、OtherType 等)如何?

【问题讨论】:

【参考方案1】:

你是对的,你需要实现一个节点数据获取器,它将从节点 id 返回一个节点。

我将假设您使用的是底层 sql 数据库:节点可以是任何类型,这意味着您不能将数据库 id 用作节点 id(因为不同类型的两个节点可能具有相同的数据库 id )。您需要构建自己的节点 ID。

构建此类 id 的一种简单方法是将对象类型和对象 id 连接起来。示例:“用户:4234”。

作为中继 graphql 服务器规范指定:

我们返回的 ID 是 base64 字符串。 ID 设计为不透明的 (唯一应该传递给节点上的 id 参数的是 在系统中的某个对象上查询 id 的不变结果),以及 对字符串进行 base64 处理是 GraphQL 中提醒查看者的有用约定 该字符串是一个不透明的标识符。

我们需要将节点 ID 编码/解码为 base 64。

以下代码尚未经过测试,但可以让您了解如何继续。

DataFetcher dataFetcher = new DataFetcher() 
            
  @Override
  Object get(DataFetchingEnvironment environment) 
    Object argument = environment.getArgument("id");
    if(argument instanceof String) 
      String node = (String) argument;
      //TODO decode base 64 nodeId
      //TODO split nodeId into String `nodeType` and Long `id`
      //TODO retrieve your object of type `nodeType` and id `id`
      
    
  

【讨论】:

【参考方案2】:

模式中的 Node 接口需要一个 TypeResolver 和 Data Fetcher 实现。

即使您提供数据获取器实现并按照 AllirionX 的建议进行连接,您仍然需要为 Node 接口提供解析器,以帮助 graphql-java 了解它应该解析哪个具体对象类型。

.type(newTypeWiring("Query").dataFetcher("node", new NodeDataFetcher()))

类似的东西-

.type(newTypeWiring("Node").typeResolver(new TypeResolver() 
                @Override
                public GraphQLObjectType getType(TypeResolutionEnvironment env) 
                    return null;
                
            ))

【讨论】:

以上是关于使用 graphql-java 构建 Relay GraphQL 服务器的主要内容,如果未能解决你的问题,请参考以下文章

graphql-java:如何在以编程方式生成 graphql 模式时添加自定义标量类型?

graphql-java 循环类型依赖

GraphQL 和 Relay 的概念

React-native + Relay.js 的稳定构建

graphql-java 中的 GraphQL 模式定义语言支持

graphql-java 中的模拟 api 响应