GraphQL的概念简单解释

由於學習了GraphQL,因此對其進行概述筆記。除了如何使用的內容外,我會依據我自己的理解記錄基本的思維方式。

问题的例子

举个例子,我们来考虑一下管理某个团队和成员的API。非常简单的情况下,我们可以这样设计。

    • チームが複数ある

 

    • チームにはユーザーが複数所属する

 

    各ユーザーは、ちょうど1つのチームに所属する

只需提前考虑一个典型要求就行了。

在REST中的一个示例

暫時需要以下以ER圖表達的資料表。

如果是REST API的话,首先可能会准备好GET /user/:id和GET /team/:id这样的选项。然后,根据应用程序的需求如“想要知道用户所属的团队”或“想要知道所属团队的用户”,来设计服务器端的端点。

在这里有一个典型的REST面板,基本上,需要根据应用程序需求来增加服务器端的端点(可以简单地提供一个API并在应用程序端组合所需的查询,但从根本上来说,随着要获取的信息类型增加,逻辑实现也会不断增加,本质上是相同的)。

以下のように記述されています:
– 在GraphQL中
– GraphQL中提供了
– GraphQL中包含有

在GraphQL中,只需注册以下类似的模式,就能够自由地构建并查询应用程序中所需的信息。

type User {
  id: Int
  name: String
  team: Team
}
type Team {
  id: Int
  name: String
  users: [User]
}
type Query {
  getUser(id: Int!): User
  getTeam(id: Int!): Team
}

查询示例

想知道ID为1的用户的名字。

query {
  getUser(id: 1) {
    name
  }
}

我想知道ID为1的用户的姓名和所属团队名称。

query {
  getUser(id: 1) {
    name
    team {
      name
    }
  }
}

我想知道ID为1的用户的姓名和所属团队名称,以及属于该团队的用户的姓名。

query {
  getUser(id: 1) {
    name
    team {
      name
      users {
        name
      }
    }
  }
}

根据所需信息的粒度,应用程序可以自行遍历对象的层次结构。在此过程中,无需在API端实现新的逻辑。由于GraphQL会自动处理跟踪所需信息的部分,因此非常方便。

机制

GraphQL的查询可以嵌套到任意深度(原则上)。当然,性能和资源是个问题。因此,可以使用延迟评估来处理具有潜在无限可能性的数据结构(也就是说,不能预先确定展开到何处为止)。而且,延迟评估可以通过闭包来轻松实现(参考)。在GraphQL中,用于延迟评估的闭包被称为解析器(Resolver)。

继续使用上述的示例,我们可以实现与以下对象和入口点相对应的函数(请注意,这只是一个代码示例,仅用于模拟氛围,需要查找和阅读类似教程的准确信息)。

type TeamResolver = {
  id: number;
  name: string;
  users: () => Promise<UserResolver[]>;
};

type UserResolver = {
  id: number;
  name: string;
  team: () => Promise<TeamResolver>;
};

export async function getUser(args: { id: number }): Promise<UserResolver> {
  const user = await userLoader.load(args.id);
  return {
    id: args.id,
    name: user.name,
    team: async () => getTeam({ id: user.teamId }),
  };
}

export async function getTeam(args: { id: number }): Promise<TeamResolver> {
  const team = await teamLoader.load(args.id);
  return {
    id: args.id,
    name: team.name,
    users: async () => Promise.all(team.userIds.map((id) => getUser({ id }))),
  };
}

在这里,TeamResolver.users和UserResolver.team的类型不是值而是函数是关键。
GraphQL解析器会根据查询创建所需的对象,但只执行被请求的属性的解析器。
通过这种方式,可以实现对任意深度嵌套查询的支持。

N+1问题

在以上的代码示例中,getTeam返回的TeamResolver.users的实现如下所示。

async () => Promise.all(team.userIds.map((id) => getUser({ id }))),

换句话说,如果要解决一个团队中的用户问题,就需要调用与团队人数相等的getUser函数。这是典型的N+1问题。为了解决这个问题,一种思路是除了getUser: id => User函数之外,再提供类似于getUsers: [id] => [User]这样的函数。然而,这样做会大大减弱GraphQL提供的查询灵活性和服务器实现的简易性等优点。

在GraphQL中,使用一个被称为DataLoader的机制来处理这种情况是很常见的。DataLoader是一个独立于GraphQL的机制,但由同样的开发者提供支持。

DataLoader是一个通用工具,它可以暂时缓存对Key-Value存储的查询。

DataLoader的用户(即我们应用的实现者)需要在构造函数中传递一个名为batchLoadFn:(keys: K[]) => (V | Error)[]的函数。这里的K和V分别表示键和值的类型。这个函数必须返回与给定键对应的值(或错误对象),正如您所见。

只要提供这个方法,DataLoader就可以提供一个load函数,该函数接受一个key并返回一个值或错误。 DataLoaer.load的作用是

    不需要每次调用时都进行数据查询,而是将发生在一定时间范围内的所有查询键批量传递给batchLoadFn函数,然后将返回的值缓存起来。

进行这种周到的动作。从而实现对数据库查询的最小化。

只要数据源能从键列表中获取对象列表,DataLoader就可以缓存Key-Value存储的查询。虽然将其方便地描述为Key-Value存储的缓存,但本质上可以是任何数据源,包括关系型数据库(通常会执行类似于select … from … where id in (…)的查询)。

广告
将在 10 秒后关闭
bannerAds