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

当使用接口和内联片段时,如何在GraphQL中解析正确的类型,graphql,Graphql,我面临一个问题,需要从\uuuResolveType内部引用父级上的已解析字段。不幸的是,我需要引用的字段不是作为父级的原始api响应的一部分,而是来自另一个字段解析程序,虽然我不需要它,但它确实需要,因此它是未定义的 但是我需要这些字段(在本例中是;obj.barCount和obj.bazCount)才能进行以下查询,所以我遇到了一个死胡同。我需要它们在resolveType函数中可用,以便在定义此字段的情况下使用它们确定要解析的类型 下面是一个例子: 我希望能够进行的graphql查询: {

我面临一个问题,需要从\uuuResolveType内部引用父级上的已解析字段。不幸的是,我需要引用的字段不是作为父级的原始api响应的一部分,而是来自另一个字段解析程序,虽然我不需要它,但它确实需要,因此它是未定义的

但是我需要这些字段(在本例中是;
obj.barCount
obj.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;
  }
}
有其他解决方案的想法吗?或者我遗漏了什么

这里有更多关于用例的信息。

在数据库中,我们有一个表“entity”。这个表非常简单,唯一真正重要的列是id、parent\u id和name。类型,然后当然可以向其附加一些附加元数据

与“实体”类似,类型是从后端管理系统中动态创建的,在后端管理系统中,您可以为具体实体分配类型

“实体”的主要目的是通过父实体id和不同的“类型”(在实体的“类型”列中)建立嵌套实体的层次结构/树。会有一些不同的元数据,但我们不要集中于此

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

在API中,我们有一个端点,在这里我们可以获取具有特定类型的所有实体(旁注:除了实体上的单一类型之外,我们还有一个端点,可以通过其分类/术语获取所有实体)

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

  • 公司(或组织、公司等)
    • 分支机构(或地区等)
    • 工厂(或建筑物、设施等)
      • 区域(或房间等)
但这种层次结构只是实现这一目标的一种方式。每一个的命名可能完全不同,根据用例的不同,您可能会将其中的一部分向上或向下移动一个级别,或者根本不使用它们

唯一确定的是,它们共享同一个数据库表,将定义类型列/字段,它们可能有子项,也可能没有子项。层次结构中的底层没有子级,而是机器。其余的只是不同的元数据,我认为我们应该忽略它们,以避免进一步复杂化

正如您所看到的,层次结构需要非常灵活和动态,因此我意识到这不是一个很好的解决方案

在这种情况下,在最低级别的“区域”中,需要有一个“machines”字段,该字段应返回一个机器列表(它们位于数据库的“machines”表中,不属于层次结构的一部分,只是与“machines”表上的“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
}

在我自己的逻辑中,我不关心实体被调用了什么,只关心所需的字段。我希望避免一个实体类型上有大量可空字段,因此我认为接口或联合有助于保持事物的分离,因此我最终使用了HasSubEntities、HasEntityCount、HasMachineCount和HasMachines,因为底部的实体是y下面不会有实体,只有底部的实体才会有机器。但在实际代码中,会有比2多得多的东西,如果我认为没有以某种方式利用接口或联合,它可能会有很多可选字段。

这里有两个独立的问题

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

第二,假设
Something
字段的类型为
Something
(或
[Something]
,等等),当您告诉GraphQL字段具有抽象类型时,您试图运行的查询将永远不会工作,因为
HasBarCount
HasBazCount
不是
Something
的子类型(接口或联合),您的意思是,该字段返回的对象类型可以是在运行时缩小为一种对象类型的几种对象类型之一。可能的类型可以是组成联合的类型,也可以是实现接口的类型

联合只能由对象类型组成,而不能由接口或其他联合组成。类似地,只有对象类型可以实现接口——其他接口或联合可能无法实现接口。因此,当将内联片段与返回抽象类型的字段一起使用时,这些内联片段的
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!]!
}
function resolveType (obj) {
  return obj.models ? 'EntityWithModels' : 'EntityWithChildren'
}
entities {
  type
  ... on EntityWithModels {
    models { ... }
  }
  ... on EntityWithChildren {
    children {
      ... on EntityWithModels {
        models { ... }
      }
      ... on EntityWithChildren {
        # etc.
      }
    }
  }
}