接口类型

这是 GraphQL Library 7 版本的文档。对于长期支持 (LTS) 版本 5,请参考 GraphQL Library 5 LTS 版本

本页介绍了如何在关系背景下使用接口,并举例说明了如何在查询和变更(Mutations)中使用它们。

数据模型

以如下图形为例,其中 Person 类型具有两种不同的关系类型,可以将其连接到 MovieSeries 类型。ACTED_IN 关系根据目标节点类型的不同而具有不同的属性。

relationship interface
图 1. 通过 ACTED_IN 关系连接到 Movie 和 Series 节点的 Person 节点模型。ACTED_IN 关系的属性在两种目标节点类型之间有所不同。

示例数据库数据

考虑一个由以下示例数据组成的数据库,它遵循上述数据模型。

Person 2Movie 2Series ActedIn
图 2. Person 节点(橙色)的示例数据库数据,该节点与两部电影(蓝色)和两部系列剧(绿色)具有 ACTED_IN 关系

接口上的关系

MovieSeries 类型非常相似。它们的共同点可以通过定义一个 Production 接口来捕获,该接口由 MovieSeries 同时实现,并在该接口上声明 actors 关系字段。

这使您可以从接口查询关系,同时支持在具体类型中定义关系详情(如类型和属性)的灵活性。

@declareRelationship

@relationship 指令只能用于对象类型的字段。

如果您想在接口上建模关系,并由具体的对象类型来实现,可以使用 @declareRelationship 指令。

在接口上声明关系字段
interface Production {
  title: String!
  actors: [Person!]! @declareRelationship
}

type Person @node {
  name: String!
}

实现已声明的关系

实现 Production 接口的具体类型必须定义自己的关系详情,例如类型和属性。这既捕获了它们的细微差别,又可以作为 Production 接口的字段进行查询。

请注意 ACTED_IN 关系在 ActedInSeriesActedIn 之间有何不同。

通过 MovieSeries 实现 Production 接口来扩展类型定义
interface Production {
  title: String!
  actors: [Person!]! @declareRelationship
}

type Movie implements Production @node {
  title: String!
  released: Int!
  actors: [Person!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN)
}

type Series implements Production @node {
  title: String!
  episodes: Int!
  actors: [Person!]! @relationship(type: "ACTED_IN", properties: "ActedInSeries", direction: IN)
}

type ActedIn @relationshipProperties {
  roles: [String!]!
}

type ActedInSeries @relationshipProperties {
  roles: [String!]!
  episodes: [Int!]!
}

type Person @node {
  name: String!
}

声明的关系必须在实现该接口的所有具体类型中实现,就像接口类型上的任何其他字段一样。例如,actors 字段必须在 MovieSeries 类型中实现,并使用 @relationship 指令进行修饰。

查询接口关系

当查询接口上声明的关系字段时,虽然可以从接口级别访问关系字段,但返回的数据基于实现该接口的具体类型。

生成的查询字段,忽略 Person 类型
# Top-level interface query fields
productions(limit: Int, offset: Int, sort: [ProductionSort!], where: ProductionWhere): [Production!]!
productionsConnection(after: String, first: Int, sort: [ProductionSort!], where: ProductionWhere): ProductionsConnection!
# Top-level concrete type query fields
movies(limit: Int, offset: Int, sort: [MovieSort!], where: MovieWhere): [Movie!]!
moviesConnection(after: String, first: Int, sort: [MovieSort!], where: MovieWhere): MoviesConnection!
series(limit: Int, offset: Int, sort: [SeriesSort!], where: SeriesWhere): [Series!]!
seriesConnection(after: String, first: Int, sort: [SeriesSort!], where: SeriesWhere): SeriesConnection!

重点关注 productionsproductionsConnection 查询字段。

示例 1. 获取带有相关 Actor 节点的 Production 节点

此查询返回最多 10 个制作内容的排序列表(即 MovieSeries 节点的混合),以及它们的相关演员。

注意使用内联片段(inline fragments)来访问特定于具体类型的字段。

query {
  productions(limit: 10, sort: { title: ASC }) {
    title
    ... on Movie {
      released
    }
    actors {
      name
    }
  }
}

假设使用本页的示例数据,查询结果如下

{
  "data": {
    "productions": [
      {
          "title": "Argo",
          "released": 2012,
          "actors": [{ "name": "Ben Affleck" }],
      },
      {
          "title": "Gone Girl",
          "released": 2014,
          "actors": [{ "name": "Ben Affleck" }],
      },
      {
          "title": "The Voyage of the Mimi",
          "actors": [{ "name": "Ben Affleck" }],
      },
      {
          "title": "Buffy the Vampire Slayer",
          "actors": [{ "name": "Ben Affleck" }],
      },
    ],
  }
}
示例 2. 获取带有相关 Actor 节点和关系属性的 Production 节点

上面的查询返回最多 10 个制作内容的排序列表(即 MovieSeries 节点的混合),以及它们的相关演员和 ACTED_IN 关系的属性。

由于存在不同的关系属性类型(ActedInActedInSeries),查询必须使用内联片段来访问关系属性。

query {
  productionsConnection(first: 10, sort: [{ title: ASC }]) {
    edges {
      node {
        title
        actorsConnection(first: 2, sort: { node: { name: ASC } }) {
          edges {
            node {
              name
            }
            properties {
              ... on ActedIn {
                roles
              }
              ... on ActedInSeries {
                roles
                episodes
              }
            }
          }
        }
      }
    }
  }
}

假设使用本页的示例数据,查询响应如下

{
  "data": {
    "productionsConnection": {
      "edges": [
        {
          "node": {
            "title": "Argo",
            "actorsConnection": {
              "edges": [{
                  "node": {
                      "name": "Ben Affleck",
                  },
                  "properties": {
                      "roles": ["Tony Mendez"],
                  },
                }],
            },
          },
        },
        {
          "node": {
            "title": "Buffy the Vampire Slayer",
            "actorsConnection": {
              "edges": [{
                "node": {
                    "name": "Ben Affleck",
                },
                "properties": {
                    "roles": ["Basketball Player #10"],
                    "episodes": 1,
                },
              }],
            },
          },
        },
        {
          "node": {
            "title": "Gone Girl",
            "actorsConnection": {
              "edges": [{
                "node": {
                    "name": "Ben Affleck",
                },
                "properties": {
                    "roles": ["Nick Dunne"],
                },
              }],
            },
          },
        },
        {
          "node": {
            "title": "The Voyage of the Mimi",
            "actorsConnection": {
              "edges": [{
                "node": {
                    "name": "Ben Affleck",
                },
                "properties": {
                    "roles": ["C.T. Granville"],
                    "episodes": 7,
                },
              }],
            },
          },
        },
      ],
    },
  },
}

指向接口的关系

对于本章开头描述的数据模型,Person 类型节点已连接到 MovieSeries 节点,但此关系尚未在 GraphQL 模式中建模。

您可以通过向 Person 类型添加指向 Production 接口的关系字段,然后在具体类型中实现关系详情来实现这一点。

使用新的关系字段扩展 Person 类型
type Person @node {
  name: String!
  born: Int!
  # relationship to an interface
  actedIn: [Production!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: OUT)
}

查询接口的关系

当查询接口类型的关系字段时,可以直接查询接口字段,因为它们在概念上由该接口的所有实现共享,例如 Productiontitle

要查询实现接口的具体类型特有的字段,必须使用内联片段,例如 MoviereleasedSeriesepisodes

示例 3. 获取所有 Person 节点及其参与演出的所有 Production 节点

注意使用内联片段来访问 MovieSeries 具体类型的特定字段。

query {
    people {
        name
        actedIn {
            title
            ... on Movie {
                released
            }
            ... on Series {
                episodes
            }
        }
    }
}

过滤接口的具体实现

当查询接口类型的关系字段时,不一定要接收所有实现类型的节点。如果需要,您可以使用 typename 过滤器请求特定的节点类型。

示例 4. 获取所有 Person 节点及其通过 Movie 类型过滤的 ACTED_IN 关系的 Production 节点

注意 typename 过滤器以及使用内联片段来访问具体类型 Movie 的特定字段。

query {
  people {
    name
    actedIn(where: { typename: [Movie] }) {
      title
      ... on Movie {
        released
      }
    }
  }
}

创建指向接口的关系

创建指向接口类型的关系时,必须在变更中指定要连接的节点的具体类型。

示例 5. 创建一个带有相关 Production 节点的 Person 节点

注意所创建节点的具体类型(MovieSeries)的规范。

mutation CreateActorAndProductions {
  createPeople(
    input: [
      {
        name: "Ben Affleck"
        actedIn: {
          create: [
            {
              edge: { roles: ["Tony Mendez"] }
              node: { Movie: { title: "Argo", released: 2012 } }
            }
            {
              edge: { roles: ["Nick Dunne"] }
              node: { Movie: { title: "Gone Girl", released: 2014 } }
            }
            {
              edge: { roles: ["Basketball Player #10"] }
              node: { Series: { title: "Buffy the Vampire Slayer", episodes: 144 } }
            }
            {
              edge: { roles: ["C.T. Granville"] }
              node: { Series: { title: "The Voyage of the Mimi", episodes: 7 } }
            }
          ]
        }
      }
    ]
  ) {
        people {
            name
        }
    }
}

更新指向接口的关系

对接口的操作适用于所有实现该接口的具体类型,除非您对其进行了限制。

Person Movie Series creation
图 3. 数据库中新断开的节点(左);期望的连接(右)

下面的变更将新发行的 Movie "Henry Danger" 添加到数据库中,并通过角色为 "Henry Hart" 的 ACTED_IN 关系将其连接到 Person 节点 Jace Norman。请注意,标题相同的 Series 节点已经存在。

要实现所需的连接,请在更新变更的过滤器中使用 typename 过滤器。

示例 6. 更新 Person 节点并创建通过 Movie 类型过滤的指向 Production 节点的 ACTED_IN 关系

注意使用 typename 过滤器仅针对 Movie 节点。

mutation {
  updatePeople(
    where: { name: { eq: "Jace Norman" } }
    update: {
      actedIn: [
        {
          connect: [
            {
              edge: { roles: ["Henry Hart"] }
              where: {
                node: {
                  typename: [Movie],
                  title: { eq: "Henry Danger" }
                },
              },
            },
          ],
        },
      ],
    }
  ) {
    info {
      relationshipsCreated
    }
  }
}

如果 PersonMovie 之间的关系已存在于数据库中,请确保关系属性根据所连接节点的类型是正确的。

示例 7. 更新 Person 节点并根据属性类型更新指向任何 Production 节点的 ACTED_IN 关系属性

注意边缘属性会根据所连接节点的 typename 更新为不同的值。

mutation {
  updatePeople(
    where: {
      name: {
        eq: "Jace Norman"
      }
    }
    update: {
      actedIn: [
        {
          update: {
            where: { node: { title: { eq: "Henry Danger" } } }
            node: {
              actors: [
                {
                  update: {
                    edge: {
                      ActedIn: { roles: { set: ["Henry Hart"] } },
                      ActedInSeries: { roles: { set: ["Henry Hart"] }, episodes: { set: 121 } },
                    },
                  },
                },
              ],
            },
          },
        }]
    }
  ) {
    info {
      relationshipsCreated
    }
  }
}