操作计划
当 Grafast 首次看到一个操作时,它会构建一个操作计划,该计划是执行计划和输出计划的组合。为此,它会遍历选择集,为每个字段调用用户提供的plan
方法,以确定需要为该字段执行的步骤。这系列步骤,我们称之为字段计划,然后与操作中的所有其他字段计划交织在一起,形成一个有向无环图,我们称之为执行计划。在执行此操作时,Grafast 会跟踪哪些字段返回了哪些步骤,并使用此信息来形成输出计划。最后,执行计划被优化和最终确定,输出计划也被最终确定,然后它们就可以执行了。
该过程的简化版本如下
- 从根选择集开始。
- 对于当前选择集中的每个字段
- 调用字段计划解析器
- 调用任何未调用的参数 applyPlan 解析器
- 去重新步骤
- 使用字段的选择集(如果有)重复步骤 2
- 树摇
- 优化
- 树摇
- 最终确定
执行计划
执行计划通常1是异步的。它负责以尽可能高效的方式获取操作所需的所有数据,并且由于去重、树摇和优化,它可能会发生很大的变化。
输出计划
执行计划获取的数据不会以任何特定格式存在,并且可能已作为操作的一部分被显著地去重和简化。输出计划负责获取此数据包并将其格式化为有效的 GraphQL 响应,包括正确地序列化所有叶子,并根据 GraphQL 规范处理空值和错误。输出计划始终同步运行,尽管在流式情况(例如 GraphQL 订阅或增量交付(@stream
/@defer
))中,输出计划的一部分可能会为底层流中的每个有效负载执行。
约束
当 Grafast 构建操作计划时,它也可能确定特定约束,这些约束决定操作计划是否可以用于将来的请求。例如,如果请求包含 @skip(if: $variable)
,则根据 $variable
是否为 true
或 false
,需要不同的操作计划。在可能的情况下,约束尽可能保持狭窄 - 例如,"变量 $foo 是一个列表" 比 "变量 $foo 是列表 [1,2,3]" 更可取 - 以最大限度地重用。
当将来某个时间看到操作时,Grafast 首先查找其约束符合请求的现有操作计划,然后再回退到创建新的操作计划。
生命周期事件
去重
一旦一个字段完全规划好,Grafast 将对其产生的新步骤进行去重,尝试用已经存在的“同级”步骤替换它们。这些“同级”步骤将通过相同的步骤类构建,并且将具有相同的依赖项。通过在此阶段(在规划子选择集之前)进行去重,我们有助于确保模式保持符合 GraphQL 规范。去重可以减少计划中的步骤数量,从而提高效率(并使计划更容易理解)。
树摇
一旦每个选择集都被完全访问并且每个字段都被规划好,执行计划就完成了。
Grafast 然后遍历输出计划、它们所需的步骤以及这些步骤的依赖项(以及它们的依赖项等等),并将它们标记为活动状态。任何未激活的步骤都是“不可达的”,因此不再需要,因此可以从执行计划中删除。
某些步骤不受树摇的影响(它们被视为始终处于活动状态),特别是包括某些系统步骤,以及任何具有副作用的步骤。
优化
一旦执行计划完成并且不必要的步骤被树摇掉,Grafast 通过调用每个支持它的步骤的 optimize
生命周期方法来优化计划。这使每个步骤有机会通过检查和与其祖先交互来用更优化的形式替换自己。
例如,如果“first”步骤要优化自身,并且其父级是“list”步骤,那么它可以简单地用“list”步骤包含的计划列表中的第一个条目替换自身。更高级的示例可能包括数据库中联接表或将选择集添加到远程 GraphQL 操作等主题。
优化方法从依赖项(叶子)开始调用,并向上遍历执行计划的有向无环图的依赖项(树干)。由于计划在优化期间只能与其祖先(而不是其后代)通信,因此这确保了它们的依赖项在优化之前一直保持类期望的状态。
优化完成后,Grafast 再次进行树形抖动,以从执行计划中删除任何不必要的步骤。
Finalize
Grafast 然后通过调用支持它的步骤上的 finalize
方法来完成计划。这使每个步骤有机会执行仅需执行一次的工作,例如
- 与数据库通信的步骤可能会编译它需要的 SQL 并将其缓存以备后用
- 执行模板化的步骤可能会构建一个优化的模板函数
- 等等
Output plan finalize
最后,输出计划被完成,构建优化的输出函数并引用最新的优化步骤。
- 如果执行计划不包含异步步骤,则可以同步执行它。不过,这种情况很少见,因为通常它会包含从远程数据源(如数据库或 Web 服务)获取数据的步骤。↩