采用 亚马逊云科技 AppSync 和 GraphQL Fusion 的基于 GraphQL 网关的

作者: 尼古拉斯·德雅科 | 202 3

将多个 GraphQL 架构组合成单个端点使开发人员能够独立开发、部署和扩展其服务,同时将其作为单个 GraphQL 架构公开。 GraphQL 社区中有许多架构组合模式和方法,包括构建时方法,例如 亚马逊云科技 A ppSync 中的 合并 API ,以及 架构 拼接 和 Apollo Federation 等 运行时方法。 合并的 API 允许客户独立开发、部署和测试其子图,同时公开单个 AppSync 端点。作为合并操作的一部分,AppSync 服务负责创建组合架构。在运行时,执行模型与单个 AppSync API 基本相同,这意味着无需额外的查询计划或多步执行。

合并的 API 是编写 AppSync API 子图的绝佳解决方案。但是,如果您的要求是编写 AppSync API 和非 AppSync API,则需要实现 GraphQL 网关。GraphQL 网关负责将多个 GraphQL 子图端点组合成一个统一的架构。在运行时,网关会接收每个 GraphQL 请求,并智能地将它们路由到不同的子图中。对网关的单个请求可能会导致向后端子图发出多个内部请求,以成功检索数据。

尽管在网关内构建 GraphQL 架构的现有方法有很多,但 GraphQL 社区缺乏开放的 GraphQL 网关规范,该规范是从头开始设计的,旨在实现可扩展性和与各种工具集的集成。最近,在GraphQLConf上,亚马逊云科技 AppSync很自豪地支持Grap hQL-Fusion的发布 ,这是麻省理工学院许可下分布式GraphQL 网关的新开放规范。GraphQL-Fusion由 Ch il licream 和 Guild创立 ,现在得到了许多与GraphQL相关的供应商和项目的支持。

符合 GraphQL Fusion 规范的网关将包括查询规划和联接数据的逻辑,并且能够将请求分发到任何 GraphQL 服务器或端点,包括任何基于 亚马逊云科技 AppSync 构建的无服务器 GraphQL API,甚至是合并的 API。GraphQL-Fusion 规范扩展了传统的联合方法,允许网关在单个 GraphQL 架构 下使用 GraphQL、REST 或 g RPC 集成 API。

在这篇博客文章中,我们将讨论 GraphQL-Fusion 规范如何简化多个架构的组合,并通过示例以运行时方法编写子图。在这个演示中,我们将多个 亚马逊云科技 AppSync 子图和一个使用开源 GraphQL 服务器 GraphQL Y og a 构建的子图结合在一起。

以下是演示架构的示意图:
Fusion Gateway

在本演示中,我们将构建一个类似于 Goodreads 的书评应用程序示例。该应用程序将需要三个不同的子图:

  1. Books Subg raph → 此子图负责将与每本书相关的数据存储在网站目录中。子图提供了查询图书元数据以及从目录中添加、更新和删除书籍的功能。这本书的子图是使用 亚马逊云科技 AppSync 和 亚马逊 Dyn amoDB 创建的,用于存储 数据。
  2. 作者子图 → 此子图负责在网站目录中存储与图书作者相关的数据。它提供了查询与作者相关的信息(包括姓名和电子邮件)以及用于在目录中添加、更新和删除作者的变更的功能。此子图也是使用 亚马逊云科技 AppSync 和亚马逊 DynamoDB 创建的。
  3. 评论子图 → 此子图负责在网站目录中存储与图书评论相关的数据。该网站的用户可以为一本书添加评论,包括评论和评分。评论子图提供了按书籍或作者查询评论以及添加、更新和删除评论的功能。在本子图中,我们选择创建非 AppSync 端点。取而代之的是,我们将添加一个 亚马逊 API Gatew ay 端点,该 端点将每个 Graphql 请求代理到使用 Typescript 编写 的 亚马逊云科技 Lam bda 函数。在这个 Lambda 函数中,我们使用开源 GraphQL 服务器 GraphQL Y oga 和亚马逊 DynamoDB 来存储评论数据。

GraphQL 网关将处理 GraphQL 请求路由到一个或多个子图。我们的 GraphQL 网关使用 Hot Chocolate Fusion 编写 ,Hot Chocolate Fusion 是.net 平台的 GraphQL Fusion我们将把网关作为托管在 亚马逊云科技 Farg at e 中的容器化应用程序进行部署。

先决条件

为了部署演示,您将需要以下内容:

  1. 一个活跃的 亚马逊云科技 账户
  2. 亚马逊云科技 CDK
  3. .NET 8.0 版本 8.0.100-preview.7
  4. Docker
  5. NPM
  6. 纱线
  7. 一个 git 客户端。

部署子图

首先,让我们克隆演示存储库并部署子图。

$ git clone https://github.com/aws-samples/aws-appsync-graphql-fusion-demo.git 
$ cd appsync-graphql-fusion-demo
$ ./deploy-subgraphs

现在,我们已经部署了子图,让我们来看看子图暴露的每个 GraphQL 架构:

子图 1:作者子图

schema {
    query: Query,
    mutation: Mutation
}

type Author {
    id: ID!
    name: String!
    bio: String
    contactEmail: String
    nationality: String
}

type AuthorConnection {
   items: [Author]
   nextToken: String
}

type Book {
    authorId: ID!
    author: Author
}

input CreateAuthorInput {
    name: String!
    bio: String
    contactEmail: String
    nationality: String
}

input DeleteAuthorInput {
    id: ID!
}

type Mutation {
    createAuthor(input: CreateAuthorInput!): Author
    deleteAuthor(input: DeleteAuthorInput!): Author
}

type Query {
    authorById(id: ID!): Author
    authors(limit: Int): AuthorConnection
    bookByAuthorId(authorId: ID!): Book
}

子图 2:书籍副图

schema {
    query: Query,
    mutation: Mutation
}

type Author {
    id: ID!
    books: BookConnection
}

type Book {
    id: ID!
    title: String!
    authorId: ID!
    genre: String
    publicationYear: Int
}

type BookConnection {
   items: [Book]
   nextToken: String 
}

input CreateBookInput {
    title: String!
    authorId: ID!
    genre: String
    publicationYear: Int
}

input DeleteBookInput {
    id: ID!
}

type Mutation {
    createBook(input: CreateBookInput!): Book
    deleteBook(input: DeleteBookInput!): Book
}

type Query {
    bookById(id: ID!): Book
    books(limit: Int): BookConnection
    authorById(id: ID!): Author
}

子图 3:评论子图

schema {
    query: Query
    mutation: Mutation
}

type Author {
    id: ID!
    reviews: ReviewConnection
}

type Book {
    id: ID!
    reviews: ReviewConnection
}

type Review {
    id: ID!
    authorId: String!
    bookId: String!
    comment: String!
    rating: Int!
}

type ReviewConnection {
    items: [Review]
    nextToken: String
}

input CreateReviewInput {
    bookId: ID!
    reviewerId: ID!
    authorId: ID!
    comment: String!
    rating: Int!
}

input DeleteReviewInput {
    id: ID!
}

type Query {
    reviewById(id: ID!): Review
    reviews(limit: Int): ReviewConnection
    authorById(id: ID!): Author
    bookById(id: ID!): Book
}

type Mutation {
    createReview(input: CreateReviewInput!): Review
    deleteReview(input: DeleteReviewInput!): Review
}

在查看我们的子图架构时,首先要注意的是,我们无需添加任何特殊指令或注释即可使其与我们的 GraphQL Gateway 兼容,这是 GraphQL Fusion 规范的主要优点之一。GraphQL-Fusion 规范的目标之一是简化网关的配置方式,不需要子图来实现任何附加协议。

架构组合是 GraphQL Fusion 规范的主要组成部分。当 Fusion 架构由多个子图 “ 组成 ” 时,它能够推断出 GraphQL 架构的语义含义,这意味着几乎不需要额外的注释。架构组合可以理解命名模式和 GraphQL 最佳实践,例如中继模式。架构组合逻辑在构建时执行,并生成单个文档,该文档为网关在运行时执行查询计划提供了所有必需的信息,以便在子图中联接数据。

以作者子图架构中的以下片段为例:

type Book {
    authorId: ID!
    author: Author
}

type Query {
    authorById(id: ID!): Author
    authors(limit: Int, nextToken: String): AuthorConnection
    bookByAuthorId(authorId: ID!): Book
}

在此示例中,我们使用 “ {type} By {key} ” 命名惯例来定义哪些查询操作可用。例如,要从该子图中检索图书类型,您必须提供一个 Au thorID 输入,该输入充当检索数据的密钥。图书类型包含一个作者字段,该字段用于将一本书的数据与其对应的作者连接起来。在其他方法中,我们可能需要使用指令对架构进行注释,以便定义这种关系以及如何使用此子图检索 Book.Author 字段。使用 GraphQL Fusion,这将由架构组合例程自动处理,因此无需进行任何更改即可将子图与网关集成。

以下是本示例中整个组合架构文档类型的简要摘要。请注意,对于 书籍 类型,架构组合例程包括字段 作者 。该字段有 @source 指令,表示该指令是在作者子图中定义的。 图书类型还包含 @resolver 指令,该指令表示可以使用传递 $book_AuthorID 变量的 bookbyAuthorID 查询来检索以作者子图为来源的字 段:

schema @fusion(version: 1) 
    @httpClient(subgraph: "Books", baseAddress: "<server endpoint url>")
    @httpClient(subgraph: "Authors", baseAddress: "<server endpoint url>")
    @httpClient(subgraph: "Reviews", baseAddress: "<server endpoint url>")
  query: Query
}

type Book @variable(subgraph: "Books", name: "Book_id", select: "id") 
    @variable(subgraph: "Reviews", name: "Book_id", select: "id") 
    @variable(subgraph: "Books", name: "Book_authorId", select: "authorId") 
    @variable(subgraph: "Authors", name: "Book_authorId", select: "authorId") 
    @resolver(subgraph: "Books", select: "{ bookById(id: $Book_id) }", arguments: [ { name: "Book_id", type: "ID!" } ]) 
    @resolver(subgraph: "Authors", select: "{ bookByAuthorId(authorId: $Book_authorId) }", arguments: [ { name: "Book_authorId", type: "ID!" } ]) 
    @resolver(subgraph: "Reviews", select: "{ bookById(id: $Book_id) }", arguments: [ { name: "Book_id", type: "ID!" } ]) {
  author: Author @source(subgraph: "Authors")
  authorId: String! @source(subgraph: "Books") @source(subgraph: "Authors")
  id: ID! @source(subgraph: "Books") @source(subgraph: "Reviews")
  reviews: ReviewConnection! @source(subgraph: "Reviews")
  title: String @source(subgraph: "Books")
}

type Query {
  authorById(id: ID!): Author 
    @variable(subgraph: "Books", name: "id", argument: "id") 
    @resolver(subgraph: "Books", select: "{ authorById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ]) 
    @variable(subgraph: "Authors", name: "id", argument: "id") 
    @resolver(subgraph: "Authors", select: "{ authorById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ]) 
    @variable(subgraph: "Reviews", name: "id", argument: "id") 
    @resolver(subgraph: "Reviews", select: "{ authorById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
 
  bookByAuthorId(authorId: ID!): Book 
    @variable(subgraph: "Authors", name: "authorId", argument: "authorId") 
    @resolver(subgraph: "Authors", select: "{ bookByAuthorId(authorId: $authorId) }", arguments: [ { name: "authorId", type: "ID!" } ])
  
  bookById(id: ID!): Book 
    @variable(subgraph: "Books", name: "id", argument: "id")
     @resolver(subgraph: "Books", select: "{ bookById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ]) 
     @variable(subgraph: "Reviews", name: "id", argument: "id") 
     @resolver(subgraph: "Reviews", select: "{ bookById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
}

撰写子图

在我们的 GraphQL-Fusion 实现中,使用开放包惯例将撰写文档元数据打包到 .fgp 文件中。为了将 Gateway 与我们的子图集成,我们使用以下脚本打包子图元数据:

./compose-gateway-schema.sh

部署 GraphQL 网关

现在网关架构已经构成,我们可以使用以下脚本部署网关端点:

./deploy-fusion-gateway.sh

记下上面脚本输出的 G raphqlGateway.graphqlGatewayEndpoint ,因为我们将在下一节中使用它:

Outputs:
GraphQLGateway.GraphQLGatewayEndpoint = _http://GraphQ-Graph-<AAA>.<region>.elb.amazonaws.com/graphql
_GraphQLGateway.GraphQLGatewayServiceLoadBalancerDNS691F4497 = _[GraphQ-Graph-<AAA>.us-west-2.elb.amazonaws.com](http://graphq-graph-ugfqnjtfi6lc-301122370.us-west-2.elb.amazonaws.com/)_GraphQLGateway.GraphQLGatewayServiceServiceURL267E9CE0 = _[http://GraphQ-Graph-UgfqNJTFI6Lc-301122370.us-west-2.elb.amazonaws.com](http://graphq-graph-ugfqnjtfi6lc-301122370.us-west-2.elb.amazonaws.com/)_

测试样本

部署网关后,您可以通过作为上一步输出提供的 G raphqlGateway.graphqlGatewayE ndpoint 对其进行访问。在浏览器中导航到端点将打开网关实现提供的 GraphQL 浏览器。选择 “ 创建文档 ” 以插入示例数据。

Banana Cake Pop

1。添加样本作者


mutation createAuthor {
    createAuthor(input: {
        name: "Mark Twain",
        bio: "Mark Twain was an American humorist, journalist, lecturer, and novelist",
        contactEmail: "markTwain@example.com",
        nationality: "USA"
    }) {
        id,
        bio,
        contactEmail,
        nationality
    }
}

示例响应:


{
  "data": {
    "createAuthor": {
      "id": "bab29018-c276-4636-840e-099e227e634f",
      "bio": "Mark Twain was an American humorist, journalist, lecturer, and novelist",
      "contactEmail": "markTwain@example.com",
      "nationality": "USA"
    }
  }
}

create Author Mutation

2。使用上面步骤中该作者的 ID 添加该作者的样本书。


mutation createBook {
    createBook(input: {
        title: "The Adventures of Tom Sawyer",
        authorId: "bab29018-c276-4636-840e-099e227e634f",
        genre: "Adventure Fiction",
        publicationYear: 1876,
    }) {
        id,
        title,
        authorId,
        genre,
        publicationYear
    }
}

示例响应:


{
  "data": {
    "createBook": {
      "id": "6490e420-a375-49a4-bb5b-1c9540e70add",
      "title": "The Adventures of Tom Sawyer",
      "authorId": "bab29018-c276-4636-840e-099e227e634f",
      "genre": "Adventure Fiction",
      "publicationYear": 1876
    }
  }
}

create Book Mutation

3。使用生成的图书 ID 和作者 ID 为这本书添加样本评论。


mutation createReview {
    createReview(input: {
        authorId: "bab29018-c276-4636-840e-099e227e634f",
        bookId: "6490e420-a375-49a4-bb5b-1c9540e70add",
        comment: "This is a great American novel about the mischievous adventures of a boy named Tom Sawyer",
        rating: 8
    }) {
        id,
        authorId,
        bookId,
        comment,
        rating
    }
}

示例响应:


{
  "data": {
    "createReview": {
      "id": "2d07d856-522f-4259-9848-0a67a14929fd",
      "authorId": "bab29018-c276-4636-840e-099e227e634f",
      "bookId": "6490e420-a375-49a4-bb5b-1c9540e70add",
      "comment": "This is a great American novel about the mischievous adventures of a boy named Tom Sawyer",
      "rating": 8
    }
}

create Review Mutation
4。运行测试查询


query GetBookData {
    bookById(id: "6490e420-a375-49a4-bb5b-1c9540e70add") {
        id,
		title,
		publicationYear,
		genre,
		author {
			id,
			name,
			bio,
			nationality
		},
		reviews {
			items {
				id,
				rating,
				comment
			}
		}
	}
}

响应示例:


{
"data": {
	"bookById": {
		"id": "6490e420-a375-49a4-bb5b-1c9540e70add",
		"title": "The Adventures of Tom Sawyer",
		"publicationYear": 1876,
		"genre": "Adventure Fiction",
		"author": {
			"id": "bab29018-c276-4636-840e-099e227e634f",
			"name": "Mark Twain",
			"bio": "Mark Twain was an American humorist, journalist, lecturer, and novelist",
			"nationality": "USA"
		},
		"reviews": {
			"items": [
			{
				"id": "2d07d856-522f-4259-9848-0a67a14929fd",
				"rating": 8,
				"comment": "This is a great American novel about the mischievous adventures of a boy named Tom Sawyer."
			}
			]
		}
	}
  }
}

getBookDataQuery

检查查询计划

Gateway IDE 允许您检查请求的查询计划,通过启用融合查询计划来识别针对后端子图执行的子查询。

Query Plan

Query Plan Reponse
此请求的查询计划表明,网关对 3 个不同的后端子图执行了 3 个请求。首先,网关使用输入 ID 并行从评论子图和图书子图中检索有关图书的数据。然后,解析包括图书作者 ID 在内的图书数据后,网关会向作者子图发送后续查询,以检索图书数据中返回相应作者 ID 的作者的作者数据。您可以在 Amazon Cloudwatch 中查看与 AW S Lambda 上运行的 AppSync 子图和 GraphQL Yoga 子图相关的请求和指标。

清理

该示例提供了用于清理所有资源的清理脚本:

./cleanup-infrastructure.sh

走得更远

随着社区共同努力完善草案,GraphQL-Fusion规范的制定正在进行中。 有关 GraphQL-Fusion 实现的更多信息,请访问发布博客。

<h2></h2>

作者简介

Nicholas is a Senior Software Engineer who has been working on 亚马逊云科技 AppSync for the past 3 years. He spends his work days focused on improving GraphQL query execution performance and weekends roaming around San Francisco with his dog Pippa.


*前述特定亚马逊云科技生成式人工智能相关的服务仅在亚马逊云科技海外区域可用,亚马逊云科技中国仅为帮助您发展海外业务和/或了解行业前沿技术选择推荐该服务。