【问题标题】:Why are edges required in a Relay/GraphQL Connection?为什么中继/GraphQL 连接中需要边?
【发布时间】:2016-01-24 06:17:47
【问题描述】:

在 Relay/GraphQL 架构配置中,一对多关系(带分页)在 tutorial example 中指定

type ShipConnection {
  edges: [ShipEdge]
  pageInfo: PageInfo!
}
type ShipEdge {
  cursor: String!
  node: Ship
}

但是,ShipEdge 建立的一对一连接似乎是多余的。为什么我们不能将光标移动到ShipConnection 并将Ship ID 的数组存储为边?

type ShipConnection {
  edges: [Ship]
  pageInfo: PageInfo!
  cursor: String!
}

在一对多关系中,每个edge 需要一个额外对象的设计决策是什么?

【问题讨论】:

    标签: graphql relayjs


    【解决方案1】:

    (更新了更多解释)

    在 GraphQL 中有 3 种方式来表示数据数组:

    1. List:当您有一个有限的关联对象列表时使用,您可以一次很好地获取所有对象。在 GraphQL SDL 中,这表示为 [Ship]
    2. 节点:当您需要对列表进行分页时使用,通常是因为可能有数千个项目。请注意,这不是 Relay 规范的一部分,因此 Relay 客户端不支持(相反,您可以将项目包装在边缘,如 #3 中所述),但其他一些客户端(例如 Apollo)更灵活,并且支持此构造(但您需要提供更多样板文件)。在 GraphQL 中,这将表示为 type ShipConnection { nodes: [Ship], pageInfo: PageInfo! }
    3. 边缘:在除了分页之外,您还需要为连接中的每个边缘提供额外信息时使用(请阅读下文了解更多详细信息)。在 GraphQL 中,您可以将其写为 type ShipConnection { edges: [ShipEdge], pageInfo: PageInfo! }

    请注意,您的 GraphQL 服务器可能支持特定关联的所有三个选项,然后客户端会选择他们想要的字段。以下是它们的外观:

    type Query {
      ships: [Ship]       // #1
      shipsConnection: [ShipConnection]
    }
    
    type ShipConnection {
      nodes: [Ship]       // #2
      edges: [ShipEdge]   // #3
      pageInfo: PageInfo!
    }
    
    type PageInfo {
      endCursor           // page-based pagination
      hasNextPage
    }
    
    type ShipEdge {
      cursor: String!     // edge-based pagination
      node: Ship
      // ... edge attributes
    }
    
    type Ship {
      // ... ship attributes
    }
    

    列表 (#1) 只有在您知道项目的数量不会增加时才应该使用(例如,如果您有一个 Post,您可能希望将 tags 作为一个列表返回,但是你不应该对comments 这样做)。要在 #2 和 #3 之间做出决定,在普通节点上使用边有两个原因:

    • 这是存储特定于边缘的属性的地方。例如,如果您有一个属于多个Groups 的User,那么在关系数据库中您将有一个包含user_idgroup_id 的UserGroup 表。此表可以具有其他属性,例如 rolejoined_at 等。GroupUserEdge 将是您可以访问这些属性的地方。

    • 为光标留一个位置。 Relay,除了基于页面的分页(使用pageInfo)外,还支持基于边缘的分页。为什么 Relay 每个边都需要一个光标?由于 Relay 智能地合并了整个应用程序的数据需求,它可能已经与您请求的相同参数建立了连接,但其中没有足够的记录。为了获取丢失的数据,它可以在某个边缘的光标之后请求连接中的数据。

      考虑到数据库也有游标,并且每个查询只有一个游标,我知道这可能会造成混淆。中继连接实际上并不是一个查询,而是一组标识查询的参数。连接边缘的光标是一组参数,用于标识连接中的位置。这是比纯查询游标更高的抽象级别(请记住,即使在可能不是数据库查询或被第 3 方系统隐藏的连接上,边缘也需要能够识别位置)。由于这种灵活性要求,一个连接的光标是不够的。

    【讨论】:

    • 是的,在“船”场景中,您可能希望在船本身上使用 createdAtcolor;我只是将这些作为字段名称的抽象示例。请注意,在某些域中,您可能有多个指向同一节点的边,并且您可能想知道何时添加了每条边(在图形意义上),因此会使用 createdAt。我使用color 作为通用属性名称,但您可以想到其他可能描述边缘性质的东西。例如weight(边缘有多重要)或creator(建立链接)等。我将编辑我的答案以避免这种混淆。
    • 这是一个有用的答案,但我仍然无法想象中继何时需要使用来自连接中间的游标来获取数据。在您有“与您请求的相同参数的连接但其中没有足够记录的连接”的情况下,最后一条边的游标就足够了。
    • 我脑海中浮现的一个例子:您获取了一个 cmets 列表,但最后一条评论被删除了。因此,要获取下一批 cmets,您需要从当前最后一个游标开始。我敢肯定还有更多的用例。关键是,Relay 尝试尽可能通用且足够健壮,以管理数据发生的任何事情。
    • @PetrBela 当您进行键集分页时,您不会受到已删除记录的影响。我不明白你为什么需要以前的 cmets 光标才能获取下一页。
    • 最后一点,我要补充一点,我确实在我的一个 API 中使用了边缘数据。但是,我发现在实践中将“关系表”转换为独立实体更容易,因为它们更易于使用。换句话说,而不是 orgs -> org_users -> users 表,Org 类型有一个用户连接,而 org_user 是边缘,最好有 orgs -> members -> users 表,其中Org type 有一个 members 连接,每个Member 都有一个关联的User
    【解决方案2】:

    edges 字段为您提供了放置每条边数据的位置。例如,您可能希望在其中放置一个creatorpriority 字段,分别描述谁添加了边缘以及关系的重要性。

    如果您不需要这种灵活性(或通过连接获得的其他功能,例如分页),您可以使用简单的GraphQLList 类型。有关连接和列表之间区别的更多信息,请参阅this answer

    【讨论】:

      【解决方案3】:

      我们写了一篇关于简单 GraphQL 架构与中继特定架构之间差异的博客文章:

      https://www.prisma.io/blog/connections-edges-nodes-in-relay-758d358aa4c7

      【讨论】:

        猜你喜欢
        • 2016-12-09
        • 2015-03-23
        • 1970-01-01
        • 1970-01-01
        • 2018-01-12
        • 2019-11-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多