解析器
Apache Server如何处理GraphQL操作
Apollo Server需要知道如何为您的模式中的每个字段填充数据,以便它可以响应该数据的请求。为此,它使用解析器。
解析器是一个负责填充您的模式中单个字段数据的函数。它可以在您定义的任何方式中填充数据,例如从后端数据库或第三方API获取数据。
如果您没有为特定的字段定义解析器,Apollo Server会自动为其定义默认的默认解析器。
定义解析器
基本语法
假设我们的服务器定义如下(非常短的)模式
type Query {numberSix: Int! # Should always return the number 6 when queriednumberSeven: Int! # Should always return 7}
我们想要为 resolvers 定义 numberSix
和 numberSeven
字段 的 Query
类型,以便它们在查询时始终返回 6
和 7
。
这些 resolvers 定义看起来是这样的:
const resolvers = {Query: {numberSix() {return 6;},numberSeven() {return 7;},},};
如这个示例所示
- 在下面的例子中,你将定义服务器所有的 resolvers 在一个单个的 JavaScript 对象中(如上方的
resolvers
所示)。这个对象被称为 解析器映射。 - 解析器映射具有顶层 字段,这些字段对应于您的架构类型(如
Query
上方所示)。 - 每个 解析器 函数属于其对应字段所属的类型。
处理参数
假设我们的服务器定义了如下架构:
type User {id: ID!name: String}type Query {user(id: ID!): User}
我们想要能够查询 user
字段并按 id
获取用户。
为了实现这一点,我们的服务器需要访问用户数据。在这个假设的例子中,假定服务器定义了以下硬编码数组
const users = [{id: '1',name: 'Elizabeth Bennet',},{id: '2',name: 'Fitzwilliam Darcy',},];
现在我们可以定义一个 解析器 用于 user
字段,如下所示:
const resolvers = {Query: {user(parent, args, contextValue, info) {return users.find((user) => user.id === args.id);},},};
如这个示例所示
- 一个解析器可以可选地接受四个位置参数:
(parent, args, contextValue, info)
. - 这里的
args
参数 是一个对象,它包含为 GraphQL 查询中 操作 提供的所有 字段 的参数。
注意,这个示例未为 没有 定义 User
字段(id
和 name
) 的解析器。那是因为 Apollo Server 为这些字段创建的默认解析器做对了:它直接从 user
解析器返回的对象中获取值。
将解析器传递给 Apollo Server
在定义了所有的解析器之后,将它们传递给 ApolloServer
构造函数(作为 resolvers
属性),同时携带你的模式定义(作为 typeDefs
属性)。
以下示例定义了一个硬编码的数据集、一个模式和解析器映射。然后初始化一个 ApolloServer
实例,并传入模式和解析器。
请注意,您可以像您想要的那样将您的resolvers定义在不同的文件和对象中,只要将它们合并到单个 resolver map 中,然后传递给ApolloServer
构造函数。
Resolver chains
每次查询要求返回一个object type的field时,查询也会要求该对象的至少一个字段(如果没有,则没有理由在查询中包含该对象)。查询总是“到底”字段的,这些字段返回一个标量,一个枚举或这些列表之一。
例如,此Product
类型的所有field都会“到底”:
type Product {id: ID!name: Stringvariants: [String!]availability: Availability!}enum Availability {AVAILABLEDISCONTINUED}
由于这个规则,每当 Apollo Server解析返回对象类型的field时,它总是会然后解析该对象的一个或多个字段。这些子字段反过来也可能会包含object type。根据您的模式,这种对象-field 调用模式可以继续到一个任意深度,创建一个称为resolver chain的结构。
示例
假设我们的服务器定义了以下模式
# A library has a branch and bookstype Library {branch: String!books: [Book!]}# A book has a title and authortype Book {title: String!author: Author!}# An author has a nametype Author {name: String!}type Query {libraries: [Library]}
以下是对该模式的有效查询:
query GetBooksByLibrary {libraries {books {author {name}}}}
此查询的结果 resolver chain 与查询本身的层次结构相匹配:
这些 resolver 以上述顺序执行,并将它们的返回值通过parent
参数传递给链中的下一个 resolver。
以下是一个代码示例,该示例使用此 resolver chain 解析上述查询:
如果我们现在更新我们的query以便也要求每本书的title
:
query GetBooksByLibrary {libraries {books {titleauthor {name}}}}
那么 resolver chain 看起来是这样的:
当一个链“发散”像这样时,每个子链都会并行执行。
Resolver arguments
Resolver函数接受四个参数:parent
, args
, contextValue
和 info
(按此顺序)。
您可以在代码中为每个参数使用任何名称,但Apollo文档使用这些名称作为规范。除了parent
之外,也可以使用父类型的名称或source
。
参数 | 描述 |
---|---|
parent | 此字段父(即此字段上一个)的 resolver 的返回值。 对于没有父的顶层字段(如 |
args | 一个对象,其中包含为此字段提供的所有 GraphQL arguments。 例如,当执行 |
contextValue | 一个对象,在整个特定操作的所有 resolver 中共享。 使用它来共享每个操作的状态,包括身份验证信息、dataloader 实例以及其他可以在 resolver 之间跟踪的内容。 |
info | 包含关于操作执行状态的信息,包括字段名称、从根到字段的路径等。 它的核心字段列在GraphQL.js 源代码中。 Apollo Server 通过添加一个 |
contextValue
参数
解reader �永远不会对contextValue
参数进行破坏性修改。这确保了所有resolver之间的一致性,并防止了意外的错误。
您的 resolver 可以通过它们的第三个位置参数访问共享的contextValue
对象。contextValue对象对所有在特定操作中执行的 resolver 都是可访问的:
import { UserAPI } from './datasources/users';const resolvers = {Query: {// Our resolvers can access the fields in contextValue// from their third argumentcurrentUser: (_, __, contextValue) => {return contextValue.dataSources.userApi.findUser(contextValue.token);},},};interface MyContext {// Context typingtoken?: String;dataSources: {userApi: UserAPI;};}const server = new ApolloServer<MyContext>({typeDefs,resolvers,});const { url } = await startStandaloneServer(server, {context: async ({ req }) => ({token: getToken(req.headers.authentication),dataSources: {userApi: new UserAPI(),},}),});
import { UserAPI } from './datasources/users';const resolvers = {Query: {// Our resolvers can access the fields in contextValue// from their third argumentcurrentUser: (_, __, contextValue) => {return contextValue.dataSources.userApi.findUser(contextValue.token);},},};const server = new ApolloServer({typeDefs,resolvers,});const { url } = await startStandaloneServer(server, {context: async ({ req }) => ({token: getToken(req.headers.authentication),dataSources: {userApi: new UserAPI(),},}),});
要了解如何管理数据库和其他数据源的连接,请参阅数据抓取。
有关更多信息,请参见共享上下文。
返回值
Apollo Server根据返回值的类型处理解析器函数的返回值:
类型 | 描述 |
---|---|
标量/对象 | 解析器可以返回单个值或对象,如定义解析器中所示。此返回值通过 |
数组 | 如果您的schema指示解析器相关联的字段包含列表,则返回一个数组。 返回数组后,Apollo Server为数组中的每个项目执行嵌套解析器。 |
null / undefined | 表示无法找到字段的值。 如果您的schema指示此解析器的字段可为null,那么操作结果在该字段的位置有一个null值。 如果此解析器的字段不可为null,Apollo Server将该字段的父级设置为null。 |
Promise | 解析器可以是异步的,并执行异步操作,例如从数据库或后端API抓取。为了支持这一点,解析器可以返回一个解析为任何其他支持的返回类型的Promise。 |
默认解析器
如果您未为特定schema字段定义解析器,Apollo Server为它定义一个默认解析器。
默认解析器函数使用以下逻辑:
例如,考虑以下schema摘录:
type Book {title: String}type Author {books: [Book]}
如果为 books 字段的解析器返回一个包含 title
字段的每个对象的数组,则您可以使用 title 字段的默认解析器。默认解析器将正确返回 parent.title
。
解析联合和接口
有一些 GraphQL 类型允许您定义一个返回多种可能的对象类型的字段(即,联合和接口)。为了解析可以返回不同对象类型的字段,您必须定义一个 __resolveType
函数来通知 Apollo Server 返回的对象类型。
解析联邦实体
请参阅 解析实体。
监控解析器性能
与所有代码一样,解析器的性能取决于其逻辑。了解您模式中哪些字段计算量较大或解析速度较慢非常重要,这样您就可以提高它们的性能或确保仅在需要时查询它们。
Apollo Studio可以与 Apollo Server 直接集成,提供字段级指标,帮助您了解图形随时间推移的性能。有关更多信息,请参阅 分析性能。