文档章节

更轻松的使用GraphQL

Figo_Zhu
 Figo_Zhu
发布于 2017/11/01 19:51
字数 1950
阅读 315
收藏 6

更轻松的使用GraphQL

引言

GraphQL是Facebook开发的一套数据查询解决方案,让我们先来看一下官方的定义:

GraphQL is a query language for your API, and a server-side runtime for executing queries by using a type system you define for your data. GraphQL isn't tied to any specific database or storage engine and is instead backed by your existing code and data.

翻译过来就是:

GraphQL是对你的API的一种查询语言,并且提供了对你采用类型系统所定义的数据进行查询的服务器端运行时方案。GraphQL并不与特定的数据库或存储引擎绑定,而是能对你现有的代码和数据进行支持。

其中有2个重点:

  1. 一种查询语言
  2. 服务器端运行时方案

在网上能找到的文章往往对第一点描述的比较详细,而且这一点也确实比较吸引人。但对于关键的第二点,如何实现这套查询机制的介绍却很难找到。

以一个简单的blog为例

假设我们的blog有以下两张表:

用户表中的数据:

uid name avatar
1 Tom https://pre00.deviantart.net/2930/th/pre/i/2014/182/a/2/tom_cat_by_1997ael-d7ougoa.png
2 Jerry https://vignette.wikia.nocookie.net/tomandjerry/images/2/29/Jerry_2.png

帖子表中的数据(考虑到允许用户修改头像,所以帖子表中不冗余作者的信息,而只有作者的ID):

pid title content authorId
1 foo xxx 1
2 bar yyy 2

然后,界面大致是上下两栏模式的,上部是帖子标题、内容等;下部是作者的名字、头像等。让我们来看一下resuful和GraphQL方案的实现对比。

restful接口

如果采用restful方案,我们通常会设计如下两个接口:

  • 查询帖子内容:GET /posts/:id
  • 查询作者信息:GET /users/:id

然后,前端先调用拉取帖子内容的接口,拿到类似如下的返回结果:

GET /posts/1

{
    "code": 0,
    "reason": "success",
    "data": {
        "pid": 1,
        "title": "foo",
        "content": "xxx",
        "authorId": 1
    }
}

然后,再根据上述结果中的authorId去调用拉取用户信息的接口,来获取作者的相关信息:

GET /users/1

{
    "code": 0,
    "reason": "success",
    "data": {
        "uid": 1,
        "name": "Tom",
        "avatar": "https://pre00.deviantart.net/2930/th/pre/i/2014/182/a/2/tom_cat_by_1997ael-d7ougoa.png"
    }
}

在Web前端这样调用问题还不大,但遇到App时,由于绘制界面是一体化的,所以必须要两个restful接口都调用完毕,才能绘制界面。

而随着需求的变化,这个页面可能还会要展现评论、评论发表者的头像,等等等等;这就会导致这里需要调用的接口越来越多,从而使得App渲染这个界面的速度越来越慢。

GraphQL方式

采用GraphQL方式,我们首先需要对数据进行类型定义:

用户定义:

# user schema
type User {
    uid : ID!
    name : String!
    avatar : String!
}

帖子定义:

# post schema
type Post {
    pid : ID!
    title : String!
    content : String!
    author : User!
}

查询定义:

type Query {
    post(id: ID): Post
}

然后,我们根据界面要求编写查询语句,因为界面要求同时展现帖子内容和作者信息,所以会有如下的GraphQL查询语句:

query {  
  post(id:1) {
    pid
    title
    content
    author {
      uid
      name
      avatar
    }
  }
}

因为数据定义中,post下的author成员是User类型的,所以我们只需要通过一次查询就能够拿到绘制界面所需的数据:

{
  "data": {
    "post": {
      "pid": "1",
      "title": "foo",
      "content": "xxx",
      "author": {
        "uid": "1",
        "name": "Tom",
        "avatar": "https://pre00.deviantart.net/2930/th/pre/i/2014/182/a/2/tom_cat_by_1997ael-d7ougoa.png"
      }
    }
  }
}

看到这里大家一定能体会到GraphQL的查询语言的爽点所在了,但网上大多数的资料也往往是继续介绍这个查询语言的更多语法,但对于服务器端如何执行查询却介绍的不够深入,所给出的简单的例子甚至是上述数据结构中的每一个成员变量都要写一个对应的resolver函数来进行查询的情况。

GraphQL 的服务器端解决方案

由于存在如上痛点,笔者在进行了相关的探索后,封装了一个使用上更简便的npm库(easy-graphql)。

easy-graphql设计了一套约定,使得开发会更便捷和规范: SQR

  • S - Schemas,即数据的类型定义
  • Q - Query,即对外提供的查询接口
  • R - Resolvers,即如何查询数据的函数实现
使用步骤
1. 根据SQR约定创建目录

按照上述约定来建立目录结构,指定的目录下存放对应的文件,比如上文blog的例子,我们建立的目录格式如下:

  1. 建立graphql目录作为根目录
  2. graphql下建立schemasresolvers两个子目录,分别用于存放数据类型定义文件和对应的查询解决实现函数文件
  3. 建立query.graphqls文件,用于对外提供的查询接口定义
graphql             # GraphQL相关定义、代码的跟目录
├── query.graphqls  # 对外提供的查询接口定义文件
├── resolvers       # 如何查询数据的函数实现文件所在目录
│   ├── post_resolver.js
│   └── user_resolver.js
└── schemas         # 数据的类型定义文件所在目录
    ├── post_schema.graphqls
    └── user_schema.graphqls
2. 创建数据类型定义(schema)文件

文件存放在graphql/schemas目录下,命名规则:xxx_schema.graphqls

帖子和用户的数据类型定义上文已有,此处不再赘述

3. 创建查询接口定义(query)文件

文件放在graphql目录下,命名为:query.graphqls

4. 创建数据查询的函数实现(reslver)文件

文件放在graphql/reslvers目录下,命名规则:xxx_resolver.js

这里就只针对上文提及的帖子内容和作者信息的查询是如何实现的(resolvers/post_reslver.js):

'use strict'

const fakeDB = require('../../fakeDB');

function fetchPostById (root, {id}, ctx) {
    // post的查询,第二个参数是查询语句中传入的
    let pid = parseInt(id);
    return fakeDB.getPostById(pid);
}

// 对post下的author字段进行查询解决的函数
function fetchUserByAuthorId (root, args, ctx) {
    // 执行完post的数据查询后,遇到需要author字段的情况,会再来调用本函数,root参数就是前一步查询完的post数据
    let uid = root.authorId;
    return fakeDB.getUserById(uid);
}

const postReolvers = {
    Query : {
        post : fetchPostById,
    },

    Post : {
        // 针对嵌套的数据结构需要的解决函数
        author : fetchUserByAuthorId,
    },
};
module.exports = postReolvers;
5. 初始化

新建一个easy-graphql对象:

const path = require('path');

const easyGraphqlModule = require('easy-graphql');

const basePath = path.join(__dirname, 'graphql');
const easyGraphqlObj = new easyGraphqlModule(basePath);
  • 可视化IDE调试

对于采用node.js来进行开发的话,GraphQL提供了可视化的图形化的Web界面来编写、调试查询语句。

express插件:express-graphql KOA插件:koa-graphql

easy-graphql配合express-graphql使用:

const express = require('express');
const graphqlHTTP = require('express-graphql');

const allSchema = easyGraphqlObj.getSchema();

// using with express-graphql middleware
app.use('/graphql', graphqlHTTP({
    schema : allSchema,
    graphiql : true,
}));

然后,就能在浏览器中,直接访问对应的网址,打开可视化IDE调试界面了,效果如下图所示:

  • 直接接口形式

使用上面的中间件方案,我们已经可以实现对外提供GraphQL查询的能力了,但往往我们的项目已经有约定好的返回数据结构了,比如:

{
    "code" : 0,
    "reason" : "success",
    "data" : {...}
}

而直接采用插件形式,并不能自定义返回的数据结构,所以easy-graphql又提供了一个直接执行GraphQL查询语句的API:

/**
 * do the GraphQL query execute
 * @param {*} requestObj -  GraphQL query object {query: "..."}
 * @param {*} context - [optional] query context
 * @returns {Promise} - GraphQL execute promise 
 */
queryGraphQLAsync(requestObj, {context})

以使用express框架为例,我们可以自己实现一个接口供前端调用:

const bodyParser = require('body-parser');
app.use(bodyParser.json()); // for parsing application/json
app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded

app.post('/restful', async (req, res) => {
    let queryObj = req.body;
    
    let result;
    try {
        // using with your restful service
        result = await easyGraphqlObj.queryGraphQL(queryObj, {context: req});
    } catch (err) {
        console.error(err);
        res.json({code : -1, reason : "GraphQL error"});
        return;
    }
    
    res.json({
        code : 0,
        reason : "success",
        data : result.data,
    });
});
完整示例

完整的代码示例,请前往gayhub上的test目录下查看,欢迎大家给这个项目点赞!

参考资料

© 著作权归作者所有

Figo_Zhu
粉丝 0
博文 3
码字总数 3554
作品 0
普陀
私信 提问
首家 GraphQL 即服务公司瞄准开源数据库 PostgreSQL

作为第一家GraphQL-as-a-Service公司,Hasura推出了其开源GraphQL引擎,这是目前唯一可立即将GraphQL-as-a-Service添加到现有基于Postgres应用程序中的解决方案。基于此,开发人员可以在几分...

局长
2018/07/23
3.4K
4
用Node.js创建安全的 GraphQL API

翻译:疯狂的技术宅 www.toptal.com/graphql/gra… 本文的目标是提供关于如何创建安全的 Node.js GraphQL API 的快速指南。 你可能会想到一些问题: 使用 GraphQL API 的目的是什么? 什么是...

前端先锋
05/07
0
0
GraphQL 基金会成立了:将托管于Linux 基金会管理

GraphQL 基金会在11月7日宣布成立,并将由 Linux 基金会管理。GraphQL 基金会将专用于完善和稳定 GraphQL 生态系统,让 GraphQL 得到广泛关注和采用。GraphQL 是 Facebook 开发的一个应用层数...

Linux就该这么学
2018/12/17
88
0
GraphQL 基金会成立,将交由 Linux 基金会管理

11月7日消息,GraphQL 基金会宣布成立,并将由 Linux 基金会管理。GraphQL 基金会将专用于完善和稳定 GraphQL 生态系统,让 GraphQL 得到广泛关注和采用。 GraphQL 是 Facebook 开发的一个应...

局长
2018/11/08
1K
5
一篇文章带你认识GraphQL

作者:方芳 阅读时间大约10~15min 什么是API 我们经常说到一个术语叫或者说接口。在和服务端交互的时候,我们会说要一个数据接口。在和客户端交互的时候交互的时候,我们会说要一个jsapi。的...

腾讯IVWEB团队
02/20
0
0

没有更多内容

加载失败,请刷新页面

加载更多

一套完整的软件开发流程是怎样的?

做什么事都需要一个流程,软件开发也不例外。 那么,一个软件从无到有到底是怎么开发的?一个软件产品的结果为什么是这样?为什么开发的速度不能再快一点。为什么程序员大多秃顶?他们有那么...

我想造火箭
34分钟前
5
0
漂亮思维导图怎样绘制?教你快速套用思维导图模板绘制d

用MindMaster软件绘制思维导图,会更加高效和美观!因为MindMaster是一款专业的思维导图软件,零基础经验的朋友花费5分钟时间就能掌握它的相关画法。以下是电脑软件思维导图画法的简单步骤。...

工具分享
36分钟前
4
0
linux 软链接与 硬链接的区别

软链接与硬链接的区别 1. 硬链接不会创建inode,即使用的inode都是一样的。软链接会创建新的inode。 2. 硬链接的访问属性和源文件一模一样,没有l的标识。软链接的访问属性写明了是l,且访问...

突突突酱
37分钟前
3
0
新特性解读 | MySQL 8.0.18 有权限控制的复制

原文:Replication with restricted privileges https://mysqlhighavailability.com/replication-with-restricted-privileges/ 作者:Pedro Figueiredo 翻译:管长龙 背景 MySQL 8.0.18 以前......

爱可生
46分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部