

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# GraphQL 中的接口和联合
<a name="interfaces-and-unions"></a>

GraphQL 类型系统支持[接口](https://graphql.org/learn/schema/#interfaces)。接口会公开特定的字段组合，类型要实施接口，必须包含这些字段。

GraphQL 类型系统还支持[联合](https://graphql.org/learn/schema/#union-types)。联合与接口相同，只是联合未定义一组通用字段。如果可能的类型没有共享的逻辑层次结构，联合通常比接口更加常用。

下一节是架构类型参考。

## 接口示例
<a name="interfaces"></a>

我们可以表示一个 `Event` 接口，它表示任何种类的活动或人群聚集。一些可能的事件类型是 `Concert`、`Conference` 和 `Festival`。这些类型具有共同特征，包括名称、举行活动的地点以及开始和结束日期。这些类型也存在差异；`Conference` 提供演讲者和研讨会列表，而 `Concert` 包含表演乐队。

在架构定义语言 (SDL) 中，`Event` 接口定义如下所示：

```
interface Event {
        id: ID!
        name : String!
        startsAt: String
        endsAt: String
        venue: Venue
        minAgeRestriction: Int
}
```

每个类型都会实施 `Event` 接口，如下所示：

```
type Concert implements Event {
    id: ID!
    name: String!
    startsAt: String
    endsAt: String
    venue: Venue
    minAgeRestriction: Int
    performingBand: String
}

type Festival implements Event {
    id: ID!
    name: String!
    startsAt: String
    endsAt: String
    venue: Venue
    minAgeRestriction: Int
    performers: [String]
}

type Conference implements Event {
    id: ID!
    name: String!
    startsAt: String
    endsAt: String
    venue: Venue
    minAgeRestriction: Int
    speakers: [String]
    workshops: [String]
}
```

如果要表示多种类型的元素，接口就很有用。例如，我们可以搜索特定地点发生的所有事件。让我们在架构中添加 `findEventsByVenue` 字段，如下所示：

```
schema {
    query: Query
}

type Query {
    # Retrieve Events at a specific Venue
    findEventsAtVenue(venueId: ID!): [Event]
}

type Venue {
    id: ID!
    name: String
    address: String
    maxOccupancy: Int
}

type Concert implements Event {
    id: ID!
    name: String!
    startsAt: String
    endsAt: String
    venue: Venue
    minAgeRestriction: Int
    performingBand: String
}

interface Event {
    id: ID!
    name: String!
    startsAt: String
    endsAt: String
    venue: Venue
    minAgeRestriction: Int
}

type Festival implements Event {
    id: ID!
    name: String!
    startsAt: String
    endsAt: String
    venue: Venue
    minAgeRestriction: Int
    performers: [String]
}

type Conference implements Event {
    id: ID!
    name: String!
    startsAt: String
    endsAt: String
    venue: Venue
    minAgeRestriction: Int
    speakers: [String]
    workshops: [String]
}
```

`findEventsByVenue` 返回 `Event` 列表。由于 GraphQL 接口字段对于所有实施类型都是通用的，所以可以选择 `Event` 接口的任何字段（`id`、`name`、`startsAt`、`endsAt`、`venue` 和 `minAgeRestriction`）。此外，只要您指定了类型，就可以使用 GraphQL [片段](https://graphql.org/learn/queries/#fragments)访问任何实施类型的字段。

让我们看一下使用该接口的 GraphQL 查询示例。

```
query {
  findEventsAtVenue(venueId: "Madison Square Garden") {
    id
    name
    minAgeRestriction
    startsAt

    ... on Festival {
      performers
    }

    ... on Concert {
      performingBand
    }

    ... on Conference {
      speakers
      workshops
    }
  }
}
```

上一查询生成一个结果列表，默认情况下，服务器会根据开始日期将事件排序。

```
{
  "data": {
    "findEventsAtVenue": [
      {
        "id": "Festival-2",
        "name": "Festival 2",
        "minAgeRestriction": 21,
        "startsAt": "2018-10-05T14:48:00.000Z",
        "performers": [
          "The Singers",
          "The Screamers"
        ]
      },
      {
        "id": "Concert-3",
        "name": "Concert 3",
        "minAgeRestriction": 18,
        "startsAt": "2018-10-07T14:48:00.000Z",
        "performingBand": "The Jumpers"
      },
      {
        "id": "Conference-4",
        "name": "Conference 4",
        "minAgeRestriction": null,
        "startsAt": "2018-10-09T14:48:00.000Z",
        "speakers": [
          "The Storytellers"
        ],
        "workshops": [
          "Writing",
          "Reading"
        ]
      }
    ]
  }
}
```

由于结果是作为单个事件集合返回的，因此，使用接口表示相同特性对结果排序非常有帮助。

## 联合示例
<a name="unions"></a>

正如前面所述，联合不定义相同的字段集。搜索结果可能表示很多不同的类型。使用 `Event` 架构，您可以定义一个 `SearchResult` 联合，如下所示：

```
type Query {
    # Retrieve Events at a specific Venue
    findEventsAtVenue(venueId: ID!): [Event]
    # Search across all content
    search(query: String!): [SearchResult]
}

union SearchResult = Conference | Festival | Concert | Venue
```

此处，要查询 `SearchResult` 联合上的任何字段，您必须使用片段：

```
query {
  search(query: "Madison") {
    ... on Venue {
      id
      name
      address
    }

    ... on Festival {
      id
      name
      performers
    }

    ... on Concert {
      id
      name
      performingBand
    }

    ... on Conference {
      speakers
      workshops
    }
  }
}
```

## 在中键入分辨率 AWS AppSync
<a name="type-resolution-in-appsynclong"></a>

类型解析是一种机制，GraphQL 引擎通过这一机制将解析后的值识别为一种特定的对象类型。

回到联合搜索示例，假设我们的查询生成结果，结果列表中的每个项目必须将自身表示为 `SearchResult` 联合定义的可能类型之一（即，`Conference`、`Festival`、`Concert` 或 `Venue`）。

由于根据 `Venue` 或 `Conference` 识别 `Festival` 的逻辑取决于应用程序要求，必须给 GraphQL 引擎一点提示，这样它才能从原始结果中识别可能的类型。

使用 AWS AppSync，此提示由名为的元字段表示`__typename`，其值对应于已识别的对象类型名称。 `__typename`对于接口或联合返回类型是必需的。

## 类型解析示例
<a name="type-resolution-example"></a>

让我们重复使用之前的架构。如果您要跟随操作，可以导航到控制台，并在 **Schema (架构)** 页面之下添加以下内容：

```
schema {
    query: Query
}

type Query {
    # Retrieve Events at a specific Venue
    findEventsAtVenue(venueId: ID!): [Event]
    # Search across all content
    search(query: String!): [SearchResult]
}

union SearchResult = Conference | Festival | Concert | Venue

type Venue {
    id: ID!
    name: String!
    address: String
    maxOccupancy: Int
}

interface Event {
    id: ID!
    name: String!
    startsAt: String
    endsAt: String
    venue: Venue
    minAgeRestriction: Int
}

type Festival implements Event {
    id: ID!
    name: String!
    startsAt: String
    endsAt: String
    venue: Venue
    minAgeRestriction: Int
    performers: [String]
}

type Conference implements Event {
    id: ID!
    name: String!
    startsAt: String
    endsAt: String
    venue: Venue
    minAgeRestriction: Int
    speakers: [String]
    workshops: [String]
}

type Concert implements Event {
    id: ID!
    name: String!
    startsAt: String
    endsAt: String
    venue: Venue
    minAgeRestriction: Int
    performingBand: String
}
```

让我们为 `Query.search` 字段附加解析器。在该`Resolvers`部分中，选择**附加**，创建一个类型*为 NONE* 的新**数据源**，然后将其命名*StubDataSource*。在此示例中，我们假设已从外部源提取了结果，并将提取的结果硬编码到请求映射模板中。

在请求映射模板窗格中，输入以下内容：

```
{
    "version" : "2018-05-29",
    "payload":
    ## We are effectively mocking our search results for this example
    [
        {
            "id": "Venue-1",
            "name": "Venue 1",
            "address": "2121 7th Ave, Seattle, WA 98121",
            "maxOccupancy": 1000
        },
        {
            "id": "Festival-2",
            "name": "Festival 2",
            "performers": ["The Singers", "The Screamers"]
        },
        {
            "id": "Concert-3",
            "name": "Concert 3",
            "performingBand": "The Jumpers"
        },
        {
            "id": "Conference-4",
            "name": "Conference 4",
            "speakers": ["The Storytellers"],
            "workshops": ["Writing", "Reading"]
        }
    ]
}
```

如果应用程序返回类型名称以作为 `id` 字段的一部分，则类型解析逻辑必须解析 `id` 字段以提取类型名称，然后将 `__typename` 字段添加到每个结果中。您可以在响应映射模板中执行该逻辑，如下所示：

**注意**  
如果使用 Lambda 数据来源，您也可以将该任务作为 Lambda 函数的一部分执行。

```
#foreach ($result in $context.result)
    ## Extract type name from the id field.
    #set( $typeName = $result.id.split("-")[0] )
    #set( $ignore = $result.put("__typename", $typeName))
#end
$util.toJson($context.result)
```

运行以下查询：

```
query {
  search(query: "Madison") {
    ... on Venue {
      id
      name
      address
    }

    ... on Festival {
        id
      name
      performers
    }

    ... on Concert {
      id
      name
      performingBand
    }

    ... on Conference {
      speakers
      workshops
    }
  }
}
```

查询生成以下结果：

```
{
  "data": {
    "search": [
      {
        "id": "Venue-1",
        "name": "Venue 1",
        "address": "2121 7th Ave, Seattle, WA 98121"
      },
      {
        "id": "Festival-2",
        "name": "Festival 2",
        "performers": [
          "The Singers",
          "The Screamers"
        ]
      },
      {
        "id": "Concert-3",
        "name": "Concert 3",
        "performingBand": "The Jumpers"
      },
      {
        "speakers": [
          "The Storytellers"
        ],
        "workshops": [
          "Writing",
          "Reading"
        ]
      }
    ]
  }
}
```

类型解析逻辑会随应用程序而改变。例如，您可以使用另一种识别逻辑，检查某些字段或字段的组合是否存在。也就是说，您可以检测是否存在 `performers` 字段，以识别 `Festival`；或利用 `speakers` 和 `workshops` 字段的组合来识别 `Conference`。最终，由您定义要使用的逻辑。