使用接口和内联片段时,如何在GraphQL中解析正确的类型

时间:2020-02-11 22:52:27

标签: graphql

我遇到了一个问题,我需要从__resolveType内部引用父项上的已解析字段。不幸的是,我需要引用的字段不是作为父对象的原始api响应的一部分,而是来自另一个字段解析器,虽然我没有关系,但确实如此,因此未定义。

但是我需要这些字段(在本示例中为obj.barCountobj.bazCount)才能进行以下查询,因此我陷入了困境。我需要它们在resolveType函数中可用,以便在定义此字段的情况下可以使用它们来确定要解析的类型。

这是一个例子:

我希望能够进行graphql查询:

{
  somethings { 
    hello
    ... on HasBarCount {
      barCount
    }
    ... on HasBazCount {
      bazCount
    }
  }
}

模式:

type ExampleWithBarCount implements Something & HasBarCount & Node {
  hello: String!
  barCount: Int
}

type ExampleWithBazCount implements Something & HasBazCount & Node {
  hello: String!
  bazCount: Int
}

interface Something {
  hello: String!
}

interface HasBarCount {
  barCount: Int
}

interface HasBazCount {
  bazCount: Int
}

解析器:

ExampleWithBarCount: {
  barCount: (obj) => {
    return myApi.getBars(obj.id).length || 0
  }
}

ExampleWithBazCount {
  bazCount: (obj) => {
    return myApi.getBazs(obj.id).length || 0
  }
}

问题:

Something: {
  __resolveType(obj) {
    console.log(obj.barCount) // Problem: this is always undefined
    console.log(obj.bazCount) // Problem: this is always undefined

    if (obj.barCount) {
      return 'ExampleWithBarCount';
    }

    if (obj.bazCount) {
      return 'ExampleWithBazCount';
    }

    return null;
  }
}

关于替代解决方案的任何想法或我缺少什么?

有关用例的更多信息。

在数据库中,我们有一个表“实体”。该表非常简单,只有真正重要的列是id,parent_id,name。类型,然后您当然可以附加一些其他元数据。

像具有“实体”一样,类型是从后端管理系统内部动态创建的,并且以后您可以为具体实体分配类型。

“实体”的主要目的是通过parent_id并使用不同的“类型”(在实体的类型列中)建立嵌套实体的层次结构/树。将有一些不同的元数据,但我们不要专注于此。

注意:实体可以命名为任何东西,类型可以是任何东西。

然后在API中,我们有一个端点,可以获取具有特定类型的所有实体(旁注:除了实体上的单一类型,我们还有一个端点,可以按其分类/术语获取所有实体)。

在第一个实现中,我通过在开发过程中从UX'er添加我在规范中拥有的所有“已知”类型来对模式进行建模。实体树可能像例如。

  • 公司(或组织,...,公司等)
    • 分支(或地区,...等)
    • 工厂(或建筑物,设施等)
      • 区域(或房间,...等)

但是这种层次结构只是可以完成的一种方法。每个名称的命名可能完全不同,您可以根据使用情况将其中一些名称上移或下移或完全不使用它们。

唯一的问题是它们共享相同的数据库表,将定义类型的列/字段,并且它们可能有也可能没有子级。层次结构中的最底层将没有子代,而是有机器。其余只是不同的元数据,我认为我们应该忽略这些元数据,以免使情况进一步复杂化。

如您所见,层次结构必须非常灵活和动态,所以我意识到这并不是我开始使用的好解决方案。

在这种情况下,最低级别的“区域”将需要一个“机器”字段,该字段应返回机器列表(它们位于数据库的“机器”表中,而不是数据库的一部分)层次结构,但仅与“机器”表上的“ entity_id”相关。

在以上层次结构中,我具有适用于所有层次的模式类型和解析器:组织,分支,工厂,区域等,但是在大多数情况下,我只是重复自己一遍,所以我认为我可以转向接口来尝试对此进行更一般化

所以不要这样做

{
  companies{
    name
    branchCount
    buildingCount
    zoneCount
    branches {
      name
      buildingCount
      zoneCount
      buildings {
        name
        zoneCount
        zones {
          name
          machines {
            name
          } 
        }
      }
    }
  }
}

并且必须为实体的所有不同命名添加架构/解析器,我认为这会起作用:

{
  entities(type: "companies") { 
    name
    ... on HasEntityCount {
      branchCount: entityCount(type: "branch")
      buildingCount: entityCount(type: "building")
      zoneCount: entityCount(type: "zone")
    }
    ... on HasSubEntities {
      entities(type: "branch") {
        name
        ... on HasEntityCount {
          buildingCount: entityCount(type: "building")
          zoneCount: entityCount(type: "zone")
        }
        ... on HasMachineCount {
          machineCount
        }
        ... on HasSubEntities {
          entities(type: "building") {
            name
            ... on HasEntityCount {
              zoneCount: entityCount(type: "zone")
            }
            ... on HasMachineCount {
              machineCount
            }
            ... on HasSubEntities {
              entities(type: "zone") {
                name
                ... on HasMachines {
                  machines
                }
              }
            }
          }
        }
      }
    }
  }
}

接口为:

interface HasMachineCount {
  machineCount: Int
}

interface HasEntityCount  {
  entitiyCount(type: String): Int
}

interface HasSubEntities {
  entities(
    type: String
  ): [Entity!]
}

interface HasMachines {
  machines: [Machine!]
}

interface Entity {
  id: ID!
  name: String!
  type: String!
}

下面的方法有效,但我真的想避免使用带有大量可选/空字段的单一类型:

type Entity {
  id: ID!
  name: String!
  type: String!
  # Below is what I want to avoid, by using interfaces
  # Imagine how this would grow
  entityCount
  machineCount
  entities
  machines
}

按照我自己的逻辑,我不在乎实体是什么,而只在乎期望的字段。我想避免使用带有多个可为空字段的单一Entity类型,所以我认为接口或联合将有助于保持事物的分离,因此我最终使用了HasSubEntities,HasEntityCount,HasMachineCount和HasMachines,因为底部实体将没有下面的实体,只有底部的实体才会有计算机。但是在实际的代码中,除了2之外,还有很多,如果我不以某种方式利用接口或联合,它最终会带有很多可选字段。

1 个答案:

答案 0 :(得分:2)

这里有两个独立的问题。

一个,GraphQL以自上而下的方式解析字段。父字段始终在任何子字段之前解析。因此,永远不可能从父字段的解析器(或“同级”字段的解析器)访问字段解析为的值。对于具有抽象类型的字段,这也适用于 type 解析器。在调用任何子解析程序之前,将解析字段类型。解决此问题的唯一方法是将相关逻辑从子解析器移动到父解析器内部。

两个,假设somethings字段的类型为Something(或[Something]等),则您尝试运行的查询将永远无法工作,因为HasBarCountHasBazCount不是Something的子类型。当您告诉GraphQL某个字段具有抽象类型(接口或联合)时,您是说该字段返回的内容可能是几种对象类型之一,这些对象类型将在运行时缩小为一种对象类型。可能的类型可以是组成联合的类型,也可以是实现接口的类型。

联合只能由对象类型组成,而不能由接口或其他联合组成。同样,只有对象类型可以实现接口,而其他接口或联合则可能不实现接口。因此,当将内联片段与返回抽象类型的字段一起使用时,这些内联片段的on条件将始终是对象类型,并且必须是所讨论的抽象类型的可能类型之一。

由于这是伪代码,因此尚不清楚您要使用这种模式进行建模的业务规则或用例。但是我可以说,通常不需要创建接口并使用类型实现它,除非您打算在模式中添加将该接口作为其类型的字段。

编辑:在较高的层次上,听起来您可能只想做这样的事情:

type Query {
  entities(type: String!): [Entity!]!
}

interface Entity {
  type: String!
  # other shared entity fields
}

type EntityWithChildren implements Entity {
  type: String!
  children: [Entity!]!
}

type EntityWithModels implements Entity {
  type: String!
  models: [Model!]!
}

类型解析器需要检查是否有模型,因此您需要确保在获取实体时获取相关模型(而不是在models解析器内部获取相关模型)。或者,您可以在数据库中添加某种列,以将实体标识为层次结构中的“最低”,在这种情况下,您可以仅使用此属性。

function resolveType (obj) {
  return obj.models ? 'EntityWithModels' : 'EntityWithChildren'
}

现在您的查询如下:

entities {
  type
  ... on EntityWithModels {
    models { ... }
  }
  ... on EntityWithChildren {
    children {
      ... on EntityWithModels {
        models { ... }
      }
      ... on EntityWithChildren {
        # etc.
      }
    }
  }
}

由于实体名称的可变性和层次结构深度的可变性,计数有些棘手。我建议,一旦它从服务器获得整个图形,就让客户端弄清楚计数。如果您确实要添加计数字段,则必须具有childrenCountgrandchildrenCount等字段。那么正确填充这些字段的唯一方法是在根目录获取整个图

相关问题