Advertisement

Extending the GraphQL Specification

阅读量:

作者:禅与计算机程序设计艺术

1.简介

GraphQL是一种用于API查询的高级编程语言。其设计灵感来源于RESTful API模式,并提供了一种声明性的查询方式来增强数据交互能力。该方法的优势在于能够有效地解决组件之间依赖关系的问题,并将复杂性转移至后端框架层面。其核心价值在于简化开发者的前端工作量,在不影响后端组织架构的前提下实现灵活的数据访问策略。作为一种广泛采用的技术,在现代软件开发中发挥着重要作用

该编程语言不仅具备强大的类型系统(type system),还可以为开发者提供精确的数据模型管理能力,并确保客户端与服务器之间的通信安全性和可靠性

2.基本概念及术语

2.1 Graphql

  • 查询语言:Graphql查询语言(简称GraphQL QL)是一种用于定义数据查询需求的高级字符串型脚本语言。
  • 类型系统:GraphQL支持基于类型系统的功能。该系统允许开发者通过明确的数据结构定义来管理信息。每个 GraphQL 类型都包含名称字段和可选属性等核心要素。
  • 请求:客户端需向GraphQL服务端发送POST请求。此操作会携带预定义格式化的 GraphQL 查询语句作为请求参数。
  • 执行器(Executor):GraphQL中的执行器负责接收并解析客户端提交的 GraphQL 查询语句。它会首先验证查询语句的语法完整性,并构建对应的抽象语法树结构。
  • 响应:服务端接收到请求后会解析并执行相应的 GraphQL 查询。完成后会返回一个包含结果或错误信息的JSON格式数据包。
  • 变量:为了提高灵活性和可扩展性(GraphQL), GraphQL允许客户端在提交至服务端前传入必要的变量参数。

2.2 Schema & Type

GraphQL服务的入口一般是一个URL地址。该架构通过一个Schema框架提供了所有可访问的数据类型及其关联属性。当客户端发起请求时,系统需要明确传输哪些数据项以及预期的数据格式. GraphQL中的类型体系由两类核心组件构成:基本类型与属性域.每一基本类型的结构由其属性域集合决定,而各属性域则明确了这些类型的可操作性与功能边界.值得注意的是,每一基本类型的属性域数量可能从零到多个不等,同时每一属性域的支持输入参数数量同样可能从零到多个不等.

2.3 Resolvers

在Graphed体系中, Resolver扮演着关键角色. Resolver负责在运行时解析并处理Graphed查询语句, 并整合结果以生成最终响应. 其主要职责是确定Graphed查询语句应返回何种类型的值. 每个Graphed数据类型都必须配置一个对应的Resolver. 其相应的执行机构会负责解析并处理这些Graphed查询.

2.4 Subscription

Subscription作为GraphQL的一个核心特性,在其生态系统中扮演着关键角色。该机制通过服务器主动向客户端推送数据流,并避免在常规查询模式下立即返回结果集。订阅采用异步轮询机制,在保障用户体验的同时实现高效的数据更新能力

2.5 Batching

Batching也被视为GraphQL的一种扩展功能。该技术使客户端能够在一个请求中同时提交多个查询,并降低了网络传输量和响应时间。

2.6 Interface Merging

除了作为GraphQL的一个扩展功能外, Interface Merging还支持不同服务提供商的GraphQL接口整合在一起, 形成一个更为统一和高效的接口.

3.原理和具体操作步骤

3.1 理解graphql语法规则

3.1.1 关键字定义

复制代码
    query{...} //查询语句,query关键字表示这是一个查询语句
    mutation{...} //修改语句,mutation关键字表示这是一个修改语句
    subscription{...} //订阅语句,subscription关键字表示这是一个订阅语句
    type{...} //类型定义,type关键字表示这是一个类型的定义
    field{...} //字段定义,field关键字表示这是一个字段的定义
    interface{...} //接口定义,interface关键字表示这是一个接口的定义
    union{...} //联合类型定义,union关键字表示这是一个联合类型的定义
    enum{...} //枚举类型定义,enum关键字表示这是一个枚举类型的定义
    input{...} //输入类型定义,input关键字表示这是一个输入类型的定义
    implements{...} //实现接口,implements关键字表示这是一个实现接口的定义
    extend{...} //扩展类型,extend关键字表示这是一个扩展类型定义
    schema{...} //定义GraphQL schema,schema关键字表示这是一个定义GraphQL schema的语句
    directive@name{...} //自定义指令,directive关键字表示这是一个自定义指令的定义
    fragment name on type{...} //片段定义,fragment关键字表示这是一个片段的定义
    $variable:type = defaultValue //变量定义,$符号表示这是一个变量的定义,冒号前面的名字表示变量名,冒号后面的类型表示变量的类型,等于号后面的是默认值。
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

3.1.2 标量类型

复制代码
    Int # 整数
    Float # 浮点数
    String # 字符串
    Boolean # 布尔值
    ID # 唯一标识符,如UID
    
      
      
      
      
    
    代码解读

3.1.3 复合类型

复制代码
    [Type] # 表示该字段可以返回一个数组,其元素类型为Type
    [Int!]! # 表示该字段可以返回一个非空且元素类型为Int的嵌套数组
    [[Int]] # 表示该字段可以返回一个二维数组,其元素类型为Int
    InputObject # 输入对象类型
    ObjectType # 对象类型
    InterfaceType # 接口类型
    UnionType # 联合类型
    EnumType # 枚举类型
    ScalarType # 标量类型
    
      
      
      
      
      
      
      
      
    
    代码解读

3.1.4 操作符

复制代码
    queryName { } //查询命名空间
    mutationName { } //修改命名空间
    subscriptionName { } //订阅命名空间
    :param_name //查询参数定义
    "Description" #注释
    FieldAlias: fieldName @Directive(arg:"value") //字段别名和指令
    fragment frag_name on TYPE {...} //片段定义
    {...frag_name } //片段引用
    Query($var_name: Type!) {...} //查询变量定义
    Mutation($var_name: Type!) {...} //修改变量定义
    Subscription($var_name: Type!) {...} //订阅变量定义
    on OperationType {... } //订阅操作类型
    &anotherType //接口合并
    |otherType //联合类型
    ...TypeName //扩展类型
    __typename //内置变量,获取当前对象类型名称
    field(arg: "value") //字段调用,括号中代表可选的参数
    [key: value] #表示映射表
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

3.1.5 查询

复制代码
    {
      myField {
    subFieldA 
    subFieldB 
      }
    }
    
      
      
      
      
      
    
    代码解读

myField是一个返回值为对象类型或数组类型的字段;其中包含两个子字段subFieldA和subFieldB;这些子字段共同决定了最终的输出结果。

3.1.6 参数

复制代码
    {
      fieldWithArg(arg: "value"){
    id
    title
      }
    }
    
      
      
      
      
      
    
    代码解读

fieldWithArg是一个具有特定参数的字段,其值设为"value";其中id和title均为其子字段。

3.1.7 变量

复制代码
    query ($userId: ID!, $postId: Int){
      user(userId: $userId){
    posts (postIds: [$postId]){
      text
    }
      }
    }
    
      
      
      
      
      
      
    
    代码解读

userId和postId都是变量,可以在多个地方被引用。

3.1.8 别名

复制代码
    {
      userList: users {
      username
      },
      postList: posts {
      text
      }
    }
    
      
      
      
      
      
      
      
    
    代码解读

userList和postList是两个别名,分别对应users和posts的查询结果。

3.1.9 字段合并

复制代码
    {
      user {
    username
    email
      }
    
      profilePic: userProfilePic(size: 50){
    url
      }
    }
    
      
      
      
      
      
      
      
      
      
    
    代码解读

用户被定义为一个根节点,在此架构中包含多个属性与关系:其中用户名和电子邮箱字段分别作为其子字段存在。同时对变量命名进行优化以提升代码可读性:将变量名称重新赋值为其别名为userProfilePic的对象,并将其返回值设定为一个独立的对象类型:该对象作为返回值供后续处理使用:而URL字段也作为其中一员出现以确保数据完整性

3.1.10 自定义指令

复制代码
    query {
      user(userId: "1"){
    firstName @capitalize
    lastName @uppercase
      }
    }
    
    directive @capitalize on FIELD_DEFINITION | FRAGMENT_SPREAD | INLINE_FRAGMENT {
      selectionSet(selections: [FIELD]) {
    if (!selectionSet ||!selectionSet.selections) return;
    
    for (const selection of selectionSet.selections) {
      switch (selection.kind) {
        case 'FragmentSpread':
          capitalizeSelectionSet(
            fragment: getFragment(selection), 
            visitedFragments: new Set(), 
          );
          break;
        case 'InlineFragment':
          capitalizeSelectionSet(
            fragment: selection.selectionSet, 
            visitedFragments: visitedFragments, 
          );
          break;
        default:
          // Capitalize only non aliased fields and fragments selections.
          const aliasOrName = selection.alias?? selection.name;
    
          if (/^[a-zA-Z][a-z]*$/.test(aliasOrName)) {
            selection.alias = aliasOrName
             .charAt(0).toUpperCase() + aliasOrName.slice(1);
          }
      }
    }
      }
    }
    
    function capitalizeSelectionSet({ fragment, visitedFragments }) {
      let visited = false;
    
      if (visitedFragments.has(fragment.name.value)) {
    visited = true;
      } else {
    visitedFragments.add(fragment.name.value);
    visited = false;
      }
    
      for (const selection of fragment.selectionSet.selections) {
    switch (selection.kind) {
      case 'FragmentSpread':
        if (!visited) {
          capitalizeSelectionSet(
            fragment: getFragment(selection), 
            visitedFragments: visitedFragments, 
          );
        }
        break;
      case 'InlineFragment':
        if (!visited) {
          capitalizeSelectionSet(
            fragment: selection.selectionSet, 
            visitedFragments: visitedFragments, 
          );
        }
        break;
      default:
        // Capitalize only non aliased fields and fragments selections.
        const aliasOrName = selection.alias?? selection.name;
    
        if (/^[a-zA-Z][a-z]*$/.test(aliasOrName)) {
          selection.alias = aliasOrName
           .charAt(0).toUpperCase() + aliasOrName.slice(1);
        }
    }
      }
    }
    
    function getFragment(spread) {
      const fragmentDoc = parse(`fragment ${spread.name.value} on ${spread.typeName.name.value} {}`);
      return fragmentDoc.definitions[0];
    }
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

自定义指令capitalize,可以修改查询语句的输出,使字段首字母大写。

复制代码
    query {
      user(userId: "1"){
    firstName @capitalize
    lastName @uppercase
      }
    }
    
      
      
      
      
      
    
    代码解读

firstName和lastName将输出为大写字母。

复制代码
    {
      "data": {
    "user": {
      "firstName": "John", 
      "lastName": "DOE"
    }
      }
    }
    
      
      
      
      
      
      
      
    
    代码解读

全部评论 (0)

还没有任何评论哟~