概述
让我们继续构建我们的accounts
子图!我们有一系列需要从单体子图迁移的字段和字段。但是如何安全地进行迁移呢?
在本课中,我们将
- 了解
@override
指令 - 了解渐进式的
@override
增量迁移方法 - 开始迁移我们的
accounts
子图类型
@override
指令
为了将字段安全地从子图迁移到另一个,我们使用@override
指令。
我们可以将@override
应用到字段上,这些字段可以是实体和操作类型的字段(例如Query
和Mutation
)。这告诉路由器,特定的字段现在由应用@override
的子图进行解析,而不是由另一个子图解析,其中的字段也被定义。
语义标记@override
接受一个名为from的参数,这个名称将是原来定义该字段的子图的名称。
以Query.user
字段为例,目前它位于monolith
子图中。我们想将它移到accounts
子图。在accounts
模式中,我们将在字段的返回类型之后添加该指示:
type Query {user(id: ID!): User @override(from: "monolith")}
monolith
子图模式可以保持不变,但是由于请求user
字段,路由器将不再调用它。
使用渐进式@override
进行增量迁移
在生产环境中,我们可能希望放慢速度,在做出这些更改的同时监控性能和问题。例如,我们可能只想将所有 操作 向 账号
子图发送 10% 或 25%。如果一切顺利且没有问题,我们可以逐步增加这个数字,直到达到完整的 100% 迁移。
为此,我们可以在 指令 的 @override
中添加另一个 参数: label
。这个 参数接受一个以 percent
开头的字符串值,后面跟着一个括号中的数字。这个数字代表由这个 子图处理的 字段流量百分比。剩余的百分比由其他(from
)子图
处理。
让我们再次审视一下 Query.user
字段。如果我们想让 账号
子图只处理 25% 的流量,我们可以这样注释:
type Query {user(id: ID!): User @override(from: "monolith", label: "percent(25)")}
单点子图
结构仍然相同,但现在 路由器将将其请求的 75% 路由到单点子图,其余的 25% 路由到 账号
子图,针对 Query.user
字段。
让我们进行迁移!
这是我们对 账号
子图的类型和 字段的列表。我们将逐一进行操作,前两个类型一起进行。然后,您将有机会自己完成剩余的工作!
用户
接口,具有id
,name
,profilePicture
主机
类型,具有id
,name
,profilePicture
和profileDescription
访客
类型,具有id
,name
,profilePicture
查询.user
查询.me
突变.updateProfile
- 更新配置所使用的类型:
UpdateProfileInput
,UpdateProfileResponse
和MutationResponse
用户
接口
让我们从用户
接口开始。我们可以在monolith/schema.graphql
文件中找到其定义。复制并将其粘贴到accounts
子图schema
中。
"Represents an Airlock user's common properties"interface User {id: ID!"The user's first and last name"name: String!"The user's profile photo URL"profilePicture: String!}
在monolith
子图中呢?嗯,我们仍然需要在那里定义用户
接口。我们将它作为Review.author
字段的返回类型来引用,我们还为Host
和Guest
类型实现此接口。
然而,我们不需要保留所有由accounts
子图负责的额外字段!我们可以删除name
和profilePicture
。
"Represents an Airlock user's common properties"interface User {id: ID!- "The user's first and last name"- name: String!- "The user's profile photo URL"- profilePicture: String!}
我们仍然需要保留字段id
,因为这是识别用户特定实例的最小必要条件(就像一个占位符!)
界面定义可以在子图中默认共享,并且每个子图中的定义可能不同。这就是我们如何从单体User
接口定义中删除子图中的name
和profilePicture
字段
现在我们已经在两个子图中解决了User
界面的问题,让我们继续处理实现类型,即Host
和Guest
。
✏️ 《Host
》实体
让我们首先看一下Host
类型及其字段。对于每个字段,我们都添加了一个注释,说明哪个子图应当负责该字段,这基于关注点分离原则。
type Host implements User @key(fields: "id") {id: ID! # the key field, a unique identifier for a particular instance of a Hostname: String! # accounts subgraphprofilePicture: String! # accounts subgraphprofileDescription: String! # accounts subgraphoverallRating: Float # reviews subgraph, eventually}
按照上述计划,我们需要做以下几件事
- 将三个字段(
name
、profilePicture
和profileDescription
)移动到
子图,该子图将负责将这些字段贡献给Host
实体。 - 单体子图将继续贡献字段 overallRating(最终, reviews子图也将这样做,但让我们一步步来!)。
让我们开始吧!
提示: 在此过程中以及在整门课程中尝试使用并排窗口。我们将从单体子图模式中拆分类型和字段(在左侧),并将它们复制到accounts
子图模式(右侧)。
从单体子图模式中,让我们复制
Host
类型、其键字段以及它将负责的三个字段(name
、profilePicture
和profileDescription
)到accounts
子图模式。subgraph-accounts/schema.graphqltype Host implements User @key(fields: "id") {id: ID!"The user's first and last name"name: String!"The user's profile photo URL"profilePicture: String!"The host's profile bio description, will be shown in the listing"profileDescription: String!}让我们从
name
字段开始并应用@override
指令。我们将包含from
参数,并将其设置为monolith
子图。name: String! @override(from: "monolith")我们还需要将指令添加到顶部的导入中。
extend schema@link(url: "https://specs.apollo.dev/federation/v2.7"import: ["@key", "@shareable", "@inaccessible", "@override"])当我们保存更改时,
rover dev
会自动检测到更改并触发一个合成...哦豁!我们遇到了2个构建错误:error[E029]: Encountered 2 build errors while trying to build a supergraph.Caused by:INVALID_FIELD_SHARING: Non-shareable field "Host.profilePicture" is resolved from multiple subgraphs: it is resolved from subgraphs "accounts" and "monolith" and defined as non-shareable in all of themINVALID_FIELD_SHARING: Non-shareable field "Host.profileDescription" is resolved from multiple subgraphs: it is resolved from subgraphs "accounts" and "monolith" and defined as non-shareable in all of themThe subgraph schemas you provided are incompatible with each other. See https://apollo.graphql.net.cn/docs/federation/errors/ for more information on resolving build errors.错误
INVALID_FIELD_SHARING
指向了两个字段:Host.profilePicture
和Host.profileDescription
。阅读描述可以解释错误的原因:我们在一个子图中重复定义了一个字段,并且它不是@shareable
字段。在两个子图中都标记为@shareable
可以修复错误,但是我们并不希望将其设置为可共享!我们知道这些字段属于accounts
子图,而不是monolith
子图;这就是为什么要迁移的理由。让我们完成应用
@override
指令到这两个字段。profilePicture: String! @override(from: "monolith")profileDescription: String! @override(from: "monolith")当我们保存更改并以
rover dev
重新合成时,我们会看到一个没有错误的成功的合成。太棒了,这是Host
实体的结尾了!
检查我们的更改
在继续之前,让我们转到本地在rover dev
router运行的https://127.0.0.1:4000。
让我们构建一个
查询
来获取特定主机的overallRating
。query User($userId: ID!) {user(id: $userId) {... on Host {overallRating}}}我们将
userId
变量设置为user-1
,这是一个主机用户的ID。{"userId": "user-1"}我们可以运行查询并返回结果,但让我们先看一眼查询计划。记住,查询计划是路由器解决GraphQL操作的步骤。点击“响应”旁边的箭头,选择查询计划。我们将以图表的形式查看它。
https://127.0.0.1:4000一个主机的
overallRating
字段属于单体subgraph
,并且从查询计划中可以看出,路由器只从这个子图获取数据。现在让我们把
name
字段添加到操作中去,这是我们最近迁移到accounts
模块的字段。query User($userId: ID!) {user(id: $userId) {... on Host {overallRatingname}}}再次运行查询,我们得到了一个新的计划!这一次,我们额外向
accounts
子图进行了数据获取。这意味着我们的迁移是成功的! 🎉https://127.0.0.1:4000我们还可以将查询计划以文本形式查看,以验证
name
字段来自accounts
子图。https://127.0.0.1:4000
太棒了,让我们继续吧!
✏️ Guest
实体
让我们同样对Guest
实体使用同样的过程。在monolith/schema.graphql
文件中,我们将查看Guest
类型,并为每个与子图有关的字段进行标注。
"A guest is a type of Airlock user. They book places to stay."type Guest implements User {id: ID! # the key field, a unique identifier for a particular instance of a Guestname: String! # accounts subgraphprofilePicture: String! # accounts subgraphfunds: Float! # payments subgraph, eventually}
按照上述计划,我们需要做以下几件事
- 将两个字段(姓名fields和fields)迁移到
accounts
子图subgraph上,该子图负责向Guest
实体entity贡献这些字段。 - 单体
monolith
子图subgraph将继续贡献funds
字段(最终,payments
子图subgraph也会,但让我们一步一步来!
继续进行这些更改。在这些步骤结束时,我们的Guest
实体entity的accounts
模式应该像这样:
"A guest is a type of Airlock user. They book places to stay."type Guest implements User @key(fields: "id") {id: ID!"The user's first and last name"name: String! @override(from: "monolith")"The user's profile photo URL"profilePicture: String! @override(from: "monolith")}
其余的类型
让我们再次审查列表
- ✅
User
界面与id、姓名、profilePicture - ✅
Host
类型有id、姓名、profilePicture和profileDescription - ✅
Guest
类型有id、姓名、profilePicture 查询.user
查询.me
突变.updateProfile
- 更新配置所使用的类型:
UpdateProfileInput
,UpdateProfileResponse
和MutationResponse
现在轮到你来实现列表的其余部分了。你可以做到的!完成后,将你的代码与我们的代码进行比较。
type Query {user(id: ID!): User @override(from: "monolith")"Currently logged-in user"me: User! @override(from: "monolith")}type Mutation {"Updates the logged-in user's profile information"updateProfile(updateProfileInput: UpdateProfileInput): UpdateProfileResponse!@override(from: "monolith")}interface MutationResponse {"Similar to HTTP status code, represents the status of the mutation"code: Int!"Indicates whether the mutation was successful"success: Boolean!"Human-readable message for the UI"message: String!}"Fields that can be updated"input UpdateProfileInput {"The user's first and last name"name: String"The user's profile photo URL"profilePicture: String"The host's profile bio description, will be shown in the listing"profileDescription: String}"The response after updating a profile"type UpdateProfileResponse implements MutationResponse {"Similar to HTTP status code, represents the status of the mutation"code: Int! @override(from: "monolith")"Indicates whether the mutation was successful"success: Boolean! @override(from: "monolith")"Human-readable message for the UI"message: String! @override(from: "monolith")"Updated user"user: User @override(from: "monolith")}
注意:@override
不需要应用到fields上的UpdateProfileInput
。这是因为输入类型是一个共享类型,自动可以在subgraphs之间共享。
不要忘记清理Query
类型:我们不再需要那个_todo
字段了。
- _todo: String @shareable @inaccessible
让我们保存我们的更改并确保在 rover dev
过程中没有错误信息;这确保了 组合 成功!
实现渐进式覆盖
通过使用 @override
指令,我们将 字段(Host
和 Guest
) 转向由 accounts
子图 解决。所有请求这些 字段 的 GraphQL 操作 都将由 accounts
子图 处理。
在教程中,我们坚持使用基本的覆盖,但您也可以扩展下面的部分以了解如何实现渐进式覆盖的说明。
练习
@override
指令的描述是正确的?关键要点
- @override指令可以应用于
fields
和根operation类型的entity字段,以表示哪个subgraph应该承担解决它的责任。 - @override指令接受一个名为
from
的参数,该参数指定最初定义field(并正在进行覆盖)subgraph的名称。 - 使用渐进式覆盖,我们可以提供带有额外
@override
指令的参数,即label。这指定了router在解决字段的情况下调用子图的百分比时间。
接下来
我们的架构看起来很棒,但我们没有任何实际的resolve来使它真正工作!让我们处理下一个问题。
分享你对这堂课的疑问和评论
您的反馈帮助我们改进!如果您遇到困难或感到困惑,请告诉我们,我们将帮助您。所有评论都是公开的,并且必须遵守 Apollo行为准则。请注意,已解决或处理的评论可能会被删除。
您需要GitHub账号以便在下面进行评论。还没有吗? 请在我们Odyssey论坛上进行评论。