概述
我们添加了User
接口以及 Host
和 Guest
实体到 accounts
子图模式,但它们需要随附 解析器!在本课中,我们将:
- 复制相关的 数据源s
- 为根字段实施解析器
- 实施接口的
__resolveType
解析器 - 为实体实施
__resolveReference
解析器函数 - 从
monolith
子图返回实体的一个表示
创建数据源
目前,我们的accounts
子图还没有连接任何数据源。但是,我们知道它需要accountsAPI
数据源来获取用户和帐户信息。
让我们来看看
datasources/accounts.js
在monolith
目录中的文件。复制文件并将其粘贴到subgraph-accounts/datasources
目录中。这让我们可以在accounts
子图中访问数据源当你在那里时也可以删除占位符
datasources.js
文件在subgraph-accounts/datasources
目录中。打开
subgraph-accounts/index.js
并在顶部添加导入:subgraph-accounts/index.jsconst AccountsAPI = require("./datasources/accounts");然后,找到我们返回
contextValue
对象以供 解析程序 使用的行。在dataSources
对象中设置AccountsAPI
subgraph-accounts/index.jsreturn {...userInfo,dataSources: {accountsAPI: new AccountsAPI(),},};在初始化
AccountsAPI
时不要忘记包含cache
以启用RESTDataSource
缓存。subgraph-accounts/index.jsreturn {...userInfo,dataSources: {accountsAPI: new AccountsAPI({ cache }),},};
太好了,我们现在可以在 解析程序 中使用 此数据源了!
解析 Query
和 Mutation
字段
首先解决我们根 字段 的 解析程序
打开
accounts 子图 中的
resolvers.js
文件,并删除使用模板提供的默认 查询 及其 解析程序 函数_todo
subgraph-accounts/resolvers.jsconst resolvers = {- Query: {- _todo: () => "TODO",- },};查找 resolvers 适用于
Query.user
、Query.me
和Mutation.updateProfile
在monolith/resolvers.js
文件中(或您可以将它们复制到下面)。我们将把它们粘贴到resolvers
映射中accounts
subgraph.subgraph-accounts/resolvers.jsQuery: {user: async (_, { id }, { dataSources }) => {const user = await dataSources.accountsAPI.getUser(id);if (!user) {throw new Error("No user found for this Id");}return user;},me: async (_, __, { dataSources, userId }) => {if (!userId) throw new AuthenticationError(authErrMessage);const user = await dataSources.accountsAPI.getUser(userId);return user;},},Mutation: {updateProfile: async (_,{ updateProfileInput },{ dataSources, userId }) => {if (!userId) throw new AuthenticationError(authErrMessage);try {const updatedUser = await dataSources.accountsAPI.updateUser({userId,userInfo: updateProfileInput,});return {code: 200,success: true,message: "Profile successfully updated!",user: updatedUser,};} catch (err) {return {code: 400,success: false,message: err.message,};}},},
解析接口
接下来让我们解决 User
接口,它需要一个 resolveType
。我们实际上已经有一个在 monolith
subgraph 中定义好了,因此我们需要做的就是将该函数复制到 accounts
subgraph.
User: {__resolveType(user) {return user.role;}},
解析实体
正如我们在Voyage I 中介绍过的,向 entity 贡献 字段 的 subgraphs 使用一种特殊的 resolver,称为 引用 resolver 来解析实体。此函数使 router 可以直接访问每个 subgraph 贡献的 entity 字段。
我们将为 Host
和 Guest
实体在 accounts
子图中实现引用 解析器。
在
resolvers
对象中,为Host
类型添加一个引用 解析器:subgraph-accounts/resolvers.jsHost: {__resolveReference: (user, { dataSources }) => {return dataSources.accountsAPI.getUser(user.id);},},我们正在使用
accountsAPI
数据源的getUser
方法,并向其传递用于检索该用户信息的用户的id
。我们为 Guest类型添加一个类似的引用 解析器。
subgraph-accounts/resolvers.jsGuest: {__resolveReference: (user, { dataSources }) => {return dataSources.accountsAPI.getUser(user.id);},},
完美,这些实体有自己的引用 解析器!
我们已用 解析器覆盖了所有 accounts
模式 字段。在测试我们的更改之前,还有一件事...
monolith
子图
在前面的课程中,我们提到 我们可以在 字段中包含其他 accounts
子图,具体来说是: Listing.host
、 Booking.guest
和 Review.author
。我们将暂时保留这些 字段在 monolith
模式中(最终,它们将迁移到各自的 listings
、 bookings
和 reviews
子图中),但我们可以更新 解析器。
目前,这些 字段的每一个 解析器都使用 Accounts API。例如:
Listing: {host: ({ hostId }, _, { dataSources }) => {return dataSources.accountsAPI.getUser(hostId);},// other resolvers}
我们想要开始消除对 Accounts 服务的依赖(毕竟,这就是 accounts
子图的用处!)。因此,我们将删除这些对 accountsAPI
的调用并返回 实体表示。
返回实体表示
如果您 回想起航行 I,实体表示形式是一个对象,路由器使用它从另一个子图表示一个特定的实体。它包含一个 __typename
属性以及实体的主键字段的值。在我们的示例中,Host
和 Guest
的主键字段都是id
。
我们将为 monolith 子图中的以下每个解析器返回实体表示形式:
Listing.host
,它返回Host
类型Booking.guest
,它返回Guest
类型Review.author
,它返回User
类型
要开始处理,请打开 resolvers.js
文件(位于monolith
目录中)。
Listing.host
让我们查找一下 Listing.host
解析器。我们将删除函数的主体,它当前调用 accountsAPI
数据源。我们现在希望这个 解析器返回 Host
实体的表示。
Listing: {host: ({ hostId }) => {return { id: hostId };},}
在这里,我们解构了 parent
参数(它是一个 Listing
对象) 以检索 hostId
属性。此 hostId
用于作为主键 字段 id
的值。
注意:我们不需要在此 实体表示中包含 __typename
字段。 路由器会自动得知如何获取 __typename
字段(因为 __typename
是元-字段,根据 GraphQL 规范,它会包含在所有对象中)。
Booking.guest
让我们对 Booking.guest
解析器执行相同的操作。在 resolvers.js
文件中查找解析器并替换函数的主体,以便它返回一个对象作为 实体表示。
Booking: {// other Booking resolversguest: ({ guestId }) => {return { id: guestId };},}
类似于 Listing.host
解析器,在这里我们解构了 parent
参数(Booking
对象)以检索 guestId
属性。该 guestId
被用作主键 字段 id
的值。
Review.author
最后,我们有 Review.author
解析器,我们对其的处理会略有不同。
在模式中, Review.author
字段 返回 User
接口,它是一种抽象类型。因此, Review.author
解析器 需要 指定返回类型是 Host
还是 Guest
,并在其 实体 表示中将其包含为 __typename
。
为了确定类型,我们需要看看特定业务领域的逻辑。我们知道,只有房客才能为房源和房东撰写评论。类似地,只有房东才能为房客撰写评论。知道这一点,我们可以调查 Review
对象的形状并确定哪个属性可能有助于我们完成该逻辑。以下是评论的样例:
{"id": "review-1","authorId": "user-2","targetId": "listing-1","bookingId": "booking-1","targetType": "LISTING","text": "Wow, what an experience! I've never stayed in a cave before, so I was a little unprepared. Luckily, this listing had all the amenities I needed to feel safe and prepared for anything.","rating": 4}
我们可以使用评论的 targetType
属性(它将是 LISTING
、GUEST
或 HOST
)来确定评论作者的类型。如果用户正在撰写有关 房源 或 房东 的评论,我们可以放心地认为该用户是 房客!
查找 Review.author
解析器 在 resolvers.js
文件中,并用以下内容替换它:
Review: {author: (review) => {let role = '';if (review.targetType === 'LISTING' || review.targetType === 'HOST') {role = 'Guest';} else {role = 'Host';}return { __typename: role, id: review.authorId, role };},},
注意:我们已将 解析器 的第一个 参数 (parent
) 重命名为 review
以明确 参数 的类型。
而对于所有 解析器 而言,它们返回的都是实体表示,到此为止了!
练习
将此架构用于以下代码挑战
interface Book {isbn: ID!title: String!genre: String!}type PictureBook implements Book @key(fields: "isbn") {isbn: ID!title: String!genre: String!numberOfPictures: IntisInColor: Boolean}type YoungAdultNovel implements Book {isbn: ID!title: String!genre: String!wordCount: IntnumberOfChapters: Int}type LibraryMember @key(fields: "id") {id: ID! @externalfaveBook: Book!}
为 LibraryMember.faveBook 编写解析器以返回实体的表示形式。每本书的底层对象都包含一个 hasPictures
属性,您可以用它来判断它是一个 PictureBook
还是一个 YoungAdultNovel
。
要点
- 任何 子图向 实体贡献 字段的都需要针对该 实体定义一个
__resolveReference
解析器函数。 - 一个 实体表示形式是一个包含实体的
__typename
和@key
字段的对象。 - 一个接口需要一个
__resolveType
解析器。
下一步
哇,这就是关于 解析器的所有内容了!在下一课中,我们将在本地测试我们的更改是否正常工作,然后再在 GraphOS中发布新的 子图!
分享你对此课程的问题和评论
您的反馈有助于我们改进!如果您陷入困境或困惑,请告诉我们,我们会帮助您的。所有评论都是公开的,并且必须遵守 Apollo 行为准则。请注意,已解决或处理的评论可能会被删除。
您需要一个 GitHub 帐户才能在下面发帖。没有吗? 改为在我们的 Odyssey 论坛中发帖。