GraphQL 的查询和变更

    本文在 node.js 的环境 ( 详见初始 GraphQL )搭建好的条件下,从零开始构建一套基础的数据操作功能。现以 Oracle 经典入门系列 soctt 数据作为基础数据,实现一套接功能。

定义 Schema

​ GraphQL 思想是以数据为中心的,定义 Schema 自然成为了开发的核心。定义 Schema 即是定义数据模型,需要根据业务制定,客户端和服务端共同维护。

/** schema **/
type Dept {
  deptno: ID!
  dname: String
  loc: String
}
type Emp {
  empno: ID!
  ename: String
  job: String
  mgr: Int
  hiredate: String
  sal: Int
  comm: String
  deptno: String!
}
type Bonus {
  ename: String
  job: String
  sal: Int
  comm: String
}
type Salgrade {
  grade: ID!
  losal: Int
  hisal: Int
}

设置相关接口的操作 Schema 和 Resolver

​ Schema 中大部分的类型都是普通对象类型,但是一个 schema 内有两个特殊类型。

schema {
  query: Query
  mutation: Mutation
}

​ 每一个 GraphQL 服务都有一个 query 类型,可能有一个 mutation 类型。这两个类型和常规对象类型无差,但是它们之所以特殊,是因为它们定义了每一个 GraphQL 查询的入口。

/** schema **/
type Query{
  getAllDepts: [Dept]
  getAllEmps: [Emp]
  getAllBonus: [Bonus]
  getAllSalgrades: [Salgrade]
}
/** resolver **/ 
const { bonus, dept, emp, salgrade } = require('./fakeDB/index.js')

const resolves = {
  getAllDepts: () => dept,
  getAllEmps: () => emp,
  getAllBonus: () => bonus,
  getAllSalgrades: () => salgrade
}
/** client **/ 
query{
	getAllDepts{
  	deptno
    dname
    loc
	}
  getAllEmps{
    empno
    ename
    job
    mgr
    hiredate
    sal
    comm
    deptno
  }
  getAllBonus{
    ename
		job
    sal
    comm
  }
  getAllSalgrades{
    grade
    losal
    hisal
  }
}
/** response **/ 
{
  "data": {
    "getAllDepts": [
      {
        "deptno": "10",
        "dname": "ACCOUNTING",
        "loc": "NEW YORK"
      },
      {
        "deptno": "20",
        "dname": "RESEARCH",
        "loc": "DALLAS"
      },
      {
        "deptno": "30",
        "dname": "SALES",
        "loc": "CHICAGO"
      },
      {
        "deptno": "40",
        "dname": "OPERATIONS",
        "loc": "BOSTON"
      }
    ],
    "getAllEmps": [
      {
        "empno": "7369",
        "ename": "SMITH",
        "job": "CLERK",
        "mgr": "7902",
        "hiredate": "17-12-1980",
        "sal": 800,
        "comm": "",
        "deptno": "20"
      },
      {
        "empno": "7499",
        "ename": "ALLEN",
        "job": "SALESMAN",
        "mgr": "7698",
        "hiredate": "20-2-1981",
        "sal": 1600,
        "comm": "300",
        "deptno": "30"
      },
      {
        "empno": "7521",
        "ename": "WARD",
        "job": "SALESMAN",
        "mgr": "7698",
        "hiredate": "22-2-1981",
        "sal": 1250,
        "comm": "500",
        "deptno": "30"
      },
      {
        "empno": "7566",
        "ename": "JONES",
        "job": "MANAGER",
        "mgr": "7839",
        "hiredate": "2-4-1981",
        "sal": 2975,
        "comm": "",
        "deptno": "20"
      },
      {
        "empno": "7654",
        "ename": "MARTIN",
        "job": "SALESMAN",
        "mgr": "7698",
        "hiredate": "28-9-1981",
        "sal": 1250,
        "comm": "1400",
        "deptno": "30"
      },
      {
        "empno": "7698",
        "ename": "BLAKE",
        "job": "MANAGER",
        "mgr": "7839",
        "hiredate": "1-5-1981",
        "sal": 2850,
        "comm": "",
        "deptno": "30"
      },
      {
        "empno": "7782",
        "ename": "CLARK",
        "job": "MANAGER",
        "mgr": "7839",
        "hiredate": "9-6-1981",
        "sal": 2450,
        "comm": "",
        "deptno": "10"
      },
      {
        "empno": "7788",
        "ename": "SCOTT",
        "job": "ANALYST",
        "mgr": "7566",
        "hiredate": "13-7-87",
        "sal": 3000,
        "comm": "",
        "deptno": "20"
      },
      {
        "empno": "7839",
        "ename": "KING",
        "job": "PRESIDENT",
        "mgr": "",
        "hiredate": "17-11-1981",
        "sal": 5000,
        "comm": "",
        "deptno": "10"
      },
      {
        "empno": "7844",
        "ename": "TURNER",
        "job": "SALESMAN",
        "mgr": "7698",
        "hiredate": "8-9-1981",
        "sal": 1500,
        "comm": "0",
        "deptno": "30"
      },
      {
        "empno": "7876",
        "ename": "ADAMS",
        "job": "CLERK",
        "mgr": "7788",
        "hiredate": "13-7-87",
        "sal": 1100,
        "comm": "",
        "deptno": "20"
      },
      {
        "empno": "7900",
        "ename": "JAMES",
        "job": "CLERK",
        "mgr": "7698",
        "hiredate": "3-12-1981",
        "sal": 950,
        "comm": "",
        "deptno": "30"
      },
      {
        "empno": "7902",
        "ename": "FORD",
        "job": "ANALYST",
        "mgr": "7566",
        "hiredate": "3-12-1981",
        "sal": 3000,
        "comm": "",
        "deptno": "20"
      },
      {
        "empno": "7934",
        "ename": "MILLER",
        "job": "CLERK",
        "mgr": "7782",
        "hiredate": "23-1-1982",
        "sal": 1300,
        "comm": "",
        "deptno": "10"
      }
    ],
    "getAllBonus": [],
    "getAllSalgrades": [
      {
        "grade": "1",
        "losal": 700,
        "hisal": 1200
      },
      {
        "grade": "2",
        "losal": 1201,
        "hisal": 1400
      },
      {
        "grade": "3",
        "losal": 1401,
        "hisal": 2000
      },
      {
        "grade": "4",
        "losal": 2001,
        "hisal": 3000
      },
      {
        "grade": "5",
        "losal": 3001,
        "hisal": 9999
      }
    ]
  }
}

传参

​ 以上演示了基础的查询与数据,但实际开发中不可能只有全量查询,更多的情况下是条件查询。

参数(Arguments)

​ 在类似 REST 的系统中,你只能传递一组简单参数 -- 请求中的 query 参数和 URL 段。但是在 GraphQL 中,每一个字段和嵌套对象都能有自己的一组参数,从而使得 GraphQL 可以完美替代多次 API 获取请求。甚至你也可以给 标量(scalar)字段传递参数,用于实现服务端的一次转换,而不用每个客户端分别转换。

/** schema **/
type Query{
	getEmpByEmpno(empno : String) : Emp
}
/** resolver **/
const resolves = {
  getEmpByEmpno: (args) => {
    return emp.find((item) => {
      return args.empno == item.empno
    })
  }
}
/** client **/
query{
  getEmpByEmpno(empno:"7934"){
    ename
    job
    deptno
  } 
}
/** response **/ 
{
  "data": {
    "getEmpByEmpno": {
      "ename": "MILLER",
      "job": "CLERK",
      "deptno": "10"
    }
  }
}

值的注意的是: 在 Schema 中,所有的参数到达 Resolver 后,将合并成一个Object 类型的参数,这与我们习惯中的函数形参不太一样。

变量(Variables)

​ 上例中参数 empno 是直接在客户端直接传值。若需要动态传值时,其中一种选择就是在客户端动态的去拼接 query ,但这显然不是一个好方法。GraphQL 拥有一级方法将动态值提取到查询之外,然后作为分离的字典传进去。这些动态值即称为变量。使用变量之前,我们得做三件事:

1. 使用 `$variableName` 替代查询中的静态值。
2. 声明 `$variableName` 为查询接受的变量之一。
3. 将 `variableName: value` 通过传输专用(通常是 JSON)的分离的变量字典中。
/** client **/
query ($empno: String){
  getEmpByEmpno(empno:$empno){
    ename
    job
    deptno
  } 
}
/** client - query variables **/
{ 
  "empno": "7934" 
}
/** response **/ 
{
  "data": {
    "getEmpByEmpno": {
      "ename": "MILLER",
      "job": "CLERK",
      "deptno": "10"
    }
  }
}

聚合查询

​ 在同一个上下文中,GraphQL 一次性请求可以获取所有所需的字段。

/** resolver **/
const resolves = {
  getDeptByDeptno: ({ deptno }) => {
    return dept.find((item) => {
      return deptno == item.deptno
    })
  },
  getEmpFromDept: ({ deptno }) => {
    return dept.filter((item) => {
      return deptno != item.deptno
    })
  }
}
/** schema **/
type Query {
  getDeptByDeptno (deptno : String) : Dept
  getEmpFromDept (deptno : String) : [Emp]
}
/** client **/
query ($deptno: String){
  getDeptByDeptno(deptno:$deptno){
    dname
  }
  getEmpFromDept(deptno:$deptno){
  	ename
  	job
  }
}
/** client - query variables **/
{ 
  "empno": "30" 
}
/** response **/ 
{
  "data": {
    "getDeptByDeptno": {
      "dname": "SALES"
    },
    "getEmpFromDept": [
      {
        "ename": "ALLEN",
        "job": "SALESMAN"
      },
      {
        "ename": "WARD",
        "job": "SALESMAN"
      },
      {
        "ename": "MARTIN",
        "job": "SALESMAN"
      },
      {
        "ename": "BLAKE",
        "job": "MANAGER"
      },
      {
        "ename": "TURNER",
        "job": "SALESMAN"
      },
      {
        "ename": "JAMES",
        "job": "CLERK"
      }
    ]
  }
}

变更

​ 变更与查询的工作方式相同,在 Mutation 类型上定义一些字段,然后这些字段将作为 mutation 根字段使用,接着就能在查询中调用。

输入类型(Input Types)

​ 上例传参中传输的是标量类型,在操作变更提交的时候往往是一整个对象进行提交。GraphQL 也支持对象类型的参数,在 Schema 中定义输入类型与定义普通类型非常相似,只需将关键字改为 input 而不是 type

/** resolver **/
const { nanoid } = require('nanoid')
const resolves = {
  increaseDept: (args) => {
    const deptno = nanoid()
    const deptInstance = {
      deptno,
      ...args.dept
    }
    return deptInstance
  }
}
/** schema **/
input deptInput {
  dname: String
  loc: String
}
type Mutation {
	increaseDept(dept:deptInput) : Dept
}
/** client **/
mutation ($dept:deptInput){
  increaseDept(dept: $dept){
    deptno
    dname
    loc
  }
}
/** client - query variables **/
{
  "dept": {
    "dname": "public relations",
    "loc": "Boston"
  }
}
/** response **/ 
{
  "data": {
    "increaseDept": {
      "deptno": "NUzaVwPX8rYizd3cz8jIm",
      "dname": "public relations",
      "loc": "Boston"
    }
  }
}
posted @ 2022-09-23 17:30  --Odyssey--  阅读(312)  评论(1)    收藏  举报