10. 实现解析器
10m

概述

我们添加了User 接口以及 HostGuest 实体到 accounts ,但它们需要随附 !在本课中,我们将:

  • 复制相关的 s
  • 为根实施
  • 实施接口的__resolveType
  • 为实体实施__resolveReference 函数
  • monolith 返回的一个表示

创建数据源

目前,我们的accounts 还没有连接任何。但是,我们知道它需要accountsAPI 来获取用户和帐户信息。

  1. 让我们来看看datasources/accounts.jsmonolith目录中的文件。复制文件并将其粘贴到subgraph-accounts/datasources目录中。这让我们可以在accounts 中访问

    当你在那里时也可以删除占位符datasources.js文件在subgraph-accounts/datasources目录中。

  2. 打开subgraph-accounts/index.js并在顶部添加导入:

    subgraph-accounts/index.js
    const AccountsAPI = require("./datasources/accounts");
  3. 然后,找到我们返回 contextValue 对象以供 使用的行。在 dataSources 对象中设置 AccountsAPI

    subgraph-accounts/index.js
    return {
    ...userInfo,
    dataSources: {
    accountsAPI: new AccountsAPI(),
    },
    };
  4. 在初始化 AccountsAPI 时不要忘记包含 cache 以启用 RESTDataSource 缓存。

    subgraph-accounts/index.js
    return {
    ...userInfo,
    dataSources: {
    accountsAPI: new AccountsAPI({ cache }),
    },
    };

太好了,我们现在可以在 中使用 了!

解析 QueryMutation 字段

首先解决我们根

  1. 打开 accounts 中的 resolvers.js 文件,并删除使用模板提供的默认 及其 函数 _todo

    subgraph-accounts/resolvers.js
    const resolvers = {
    - Query: {
    - _todo: () => "TODO",
    - },
    };
  2. 查找 适用于 Query.userQuery.meMutation.updateProfilemonolith/resolvers.js 文件中(或您可以将它们复制到下面)。我们将把它们粘贴到 resolvers 映射中 accounts .

    subgraph-accounts/resolvers.js
    Query: {
    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 中定义好了,因此我们需要做的就是将该函数复制到 accounts .

subgraph-accounts/resolvers.js
User: {
__resolveType(user) {
return user.role;
}
},

解析实体

正如我们在Voyage I 中介绍过的,向 贡献 使用一种特殊的 ,称为 引用 resolver 来解析实体。此函数使 可以直接访问每个 贡献的

我们将为 HostGuest实体在 accounts 中实现引用

  1. resolvers对象中,为 Host类型添加一个引用

    subgraph-accounts/resolvers.js
    Host: {
    __resolveReference: (user, { dataSources }) => {
    return dataSources.accountsAPI.getUser(user.id);
    },
    },

    我们正在使用 accountsAPI getUser方法,并向其传递用于检索该用户信息的用户的 id

  2. 我们为 类型添加一个类似的引用

    subgraph-accounts/resolvers.js
    Guest: {
    __resolveReference: (user, { dataSources }) => {
    return dataSources.accountsAPI.getUser(user.id);
    },
    },

完美,这些实体有自己的引用

我们已用 覆盖了所有 accounts模式 。在测试我们的更改之前,还有一件事...

monolith 子图

在前面的课程中,我们提到 我们可以中包含其他 accounts ,具体来说是: Listing.hostBooking.guestReview.author。我们将暂时保留这些 monolith模式中(最终,它们将迁移到各自的 listingsbookingsreviews 中),但我们可以更新

目前,这些 的每一个 都使用 Accounts API。例如:

monolith/resolvers.js
Listing: {
host: ({ hostId }, _, { dataSources }) => {
return dataSources.accountsAPI.getUser(hostId);
},
// other resolvers
}

我们想要开始消除对 Accounts 服务的依赖(毕竟,这就是 accounts 的用处!)。因此,我们将删除这些对 accountsAPI的调用并返回 表示。

返回实体表示

如果您 回想起航行 I表示形式是一个对象,使用它从另一个表示一个特定的实体。它包含一个 __typename属性以及的主键的值。在我们的示例中,HostGuest的主键字段都是id

我们将为 中的以下每个返回实体表示形式:

  • Listing.host,它返回Host类型
  • Booking.guest,它返回Guest类型
  • Review.author,它返回User类型

要开始处理,请打开 resolvers.js文件(位于monolith目录中)。

Listing.host

让我们查找一下 Listing.host 。我们将删除函数的主体,它当前调用 accountsAPI 。我们现在希望这个 返回 Host 的表示。

monolith/resolvers.js
Listing: {
host: ({ hostId }) => {
return { id: hostId };
},
}

在这里,我们解构了 parent (它是一个 Listing 对象) 以检索 hostId 属性。此 hostId 用于作为主键 id的值。

注意:我们不需要在此 表示中包含 __typename 会自动得知如何获取 __typename (因为 __typename 是元-,根据 规范,它会包含在所有对象中)。

Booking.guest

让我们对 Booking.guest 执行相同的操作。在 resolvers.js 文件中查找解析器并替换函数的主体,以便它返回一个对象作为 表示。

monolith/resolvers.js
Booking: {
// other Booking resolvers
guest: ({ 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 属性(它将是 LISTINGGUESTHOST)来确定评论作者的类型。如果用户正在撰写有关 房源房东 的评论,我们可以放心地认为该用户是 房客

查找 Review.author resolvers.js 文件中,并用以下内容替换它:

monolith/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: Int
isInColor: Boolean
}
type YoungAdultNovel implements Book {
isbn: ID!
title: String!
genre: String!
wordCount: Int
numberOfChapters: Int
}
type LibraryMember @key(fields: "id") {
id: ID! @external
faveBook: Book!
}
代码挑战!

为 LibraryMember.faveBook 编写解析器以返回实体的表示形式。每本书的底层对象都包含一个 hasPictures 属性,您可以用它来判断它是一个 PictureBook 还是一个 YoungAdultNovel

要点

  • 任何 贡献 的都需要针对该 定义一个 __resolveReference 函数。
  • 一个 表示形式是一个包含实体的 __typename@key 的对象。
  • 一个接口需要一个 __resolveType

下一步

哇,这就是关于 的所有内容了!在下一课中,我们将在本地测试我们的更改是否正常工作,然后再在 中发布新的

上一个

分享你对此课程的问题和评论

您的反馈有助于我们改进!如果您陷入困境或困惑,请告诉我们,我们会帮助您的。所有评论都是公开的,并且必须遵守 Apollo 行为准则。请注意,已解决或处理的评论可能会被删除。

您需要一个 GitHub 帐户才能在下面发帖。没有吗? 改为在我们的 Odyssey 论坛中发帖。