一篇文章带你认识GraphQL

腾讯科技(深圳)有限公司

作者:方芳

阅读时间大约10~15min

什么是API

我们经常说到一个术语叫API或者说接口。在和服务端交互的时候,我们会说要一个数据接口。在和客户端交互的时候交互的时候,我们会说要一个jsapi。API的解释是这样的:APIApplication Programming Interface(应用程序编程接口)。顾名思义,它是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。

什么是GraphQL

GraphQLFacebook开发的API查询语言,而不是一个数据库。它为我们提供了一种更有效的设计、创建和使用 API的方法。从根本上说,它是 REST 的替代品。

GraphQL对前端很友好,使前端开发人员轻松,GraphQL也有一系列的前端库(Apollo、Relay或Urql),前端可以用如缓存、实时更新UI等功能。

借助GraphQL前端开发人员生产力可以提高,产品开发速度加快,无论UI如何变后端不用变。

Facebook发布GraphQL三年以来,社区也已经比较完善,也有很多公司在用。

GraphQL的使用

用游戏列表来举例说明一下GraphQL的使用。

数据准备

首先我们要准备的是服务端的游戏列表的数据,需要注意的是,这个数据可以来自数据库、第三方、我们的上游服务端等,GraphQL自身并不是一个数据库

[
    { id: 1, game_name: "风暴魔域(注册送好礼)", short_name: "moyu", score: 4.2625498 },
    { id: 2, game_name: "侍魂胧月传说", short_name: "shihun", score: 4.3800301 }
]
复制代码

接下来需要介绍几个GraphQL中常见的特性。

schema(模式)

GraphQL 有自己的语言类型,用于编写模式。 这是一种可读很高的模式语法,称为规范与描述语言(SDL)。无论使用何种技术,SDL 都是相同的,也就是说你可以将其用于你想要的任何语言或框架。

这种模式语言非常有用,因为它更直观的看出 API 具有哪些类型,可读性很强。

使用graphql的第一步,我们需要在graphql服务器编写我们自己的schema。

type Game {
  id: ID!
  game_name: String!
  short_name: String!
  score: Int
}

type Query {
  games: [Game!]!
  game(id: ID!): Game!
}

type Mutation {
  createGame(id: ID!, game_name: String!, short_name: String!, score: Int): Game!
  updateGame(id: ID!, game_name: String, short_name: String, score: Int): Game!
  deleteGame(id: ID!): Game!
}
复制代码

type(类型)

类型是 GraphQL 最重要的特性之一。类型是表示 API 外观的自定义对象。例如,在游戏大厅中,我们需要有用户、游戏、礼包、评论等类型。

type Game {
  id: ID!
  game_name: String!
  short_name: String!
  score: Int
}
复制代码

类型具有字段,这些字段返回特定类型的数据。 例如,我们在上面创建的 Game 类型,我们有一些 game_name,short_name 和 score 字段。 类型字段可以是任何类型,并始终返回一种数据类型,如 Int,Float,String,Boolean,ID,对象类型列表或自定义对象类型。

其中!的意思是,这个字段不能为空,所以上面的Game类型中,只有score是可以为空的。

query(查询)

query是GraphQL 中的获取数据的方式。关于 GraphQL 中的查询,最吸引人的地方之一就是你可以获得所需的确切数据,不多不少。这个点我觉得真是的非常的棒,因为我们现在在定义接口的时候有一个消耗在沟通上的点就是多方协调入参和参数返回。

type Query {
  games: [Game!]!
  game(id: ID!): Game!
}
复制代码

在这里我们定义了两个查询,一个是查询所有游戏,一个是查询指定id的游戏。

mutation(更改,增删改)

在 GraphQL 中,更改是修改服务器上的数据并获取更新数据的方式, 可以认为是我们平常用的增删改。

type Mutation {
  createGame(id: ID!, game_name: String!, short_name: String!, score: Int): Game!
  updateGame(id: ID!, game_name: String, short_name: String, score: Int): Game!
  deleteGame(id: ID!): Game!
}
复制代码

subscription(订阅)

由于实时通信业务的需求,GraphQL现在也提供了subscription的功能。可以实现服务端对客户端的实时数据传递,意味着无论何时在服务器中发生数据变化,并且每当调用该事件时,服务器都会将相应的数据发送到客户端。通过订阅,你可以让你的应用在不同的用户之间保持更新。

基本的订阅的schema是这样写的:

subscription {
  games {
    id
    game_name
    short_name
    score
  }
}
复制代码

讲完了graphql的几个常用的特性之后,接下来要来描述一下,我们在服务器端是怎么告知graphql我们需要的数据在哪里的。

resolver(解析器)

GraphQL社区提供了一个支持http的中间件,express-graphql,借助此可以很简单实现一个支持graphql的服务器,因为本次不主要介绍服务器的搭建,有兴趣可以查阅github。GraphQL的发明最初是为了提升前端的开发效率,这篇文章所讲只描述在前端中的使用,但是graphql是可以在其他语言中使用的,如果大家有兴趣也可以自行查阅。

在服务器端需要有一个resolvers的js文件,作用是用来告知graphql从哪里去取数据,并且描述对数据的处理。

import { games } from "./db";

const resolvers = {
  Query: {
    game: (parent, { id }, context, info) => {
      return games.find(user => game.id == id);
    },
    games: (parent, args, context, info) => {
      return games;
    }
  },
  Mutation: {
    createGame: (parent, { id, game_name, short_name, score }, context, info) => {
      const newGame = { id, game_name, short_name, score };

      games.push(newGame);

      return newGame;
    },
    updateGame: (parent, { id, game_name, short_name, score }, context, info) => {
      let newGame = games.find(game => game.id === id);

      newGame.game_name = game_name;
      newGame.short_name = short_name;
      newGame.score = score;

      return newGame;
    },
    deleteGame: (parent, { id }, context, info) => {
      const gameIndex = games.findIndex(game => game.id === id);

      if (gameIndex === -1) throw new Error("Game not found.");

      const deletedGames = games.splice(gameIndex, 1);

      return deletedGames[0];
    }
}
};

export default resolvers;
复制代码

以上是对应我们上面的schema的一份完整的resolver文件。

客户端该怎么写

上面讲的都是服务器端的设置与操作,那客户端应该以什么样的规则来与服务端交互。

以请求游戏列表为例:

query {
  games {
    id
    game_name
    short_name
    score
  }
}
复制代码

如果要请求某个游戏:

query {
  game(id: 1) {
    id
    game_name
    short_name
    score
  }
}
复制代码

如果需要创建一个游戏

mutation {
  createGame(id: 3, game_name: "王者荣耀", short_name: "yxzj", score: 4.7686622) {
    id
    game_name
    short_name
    score
  }
}
复制代码

通过HTTP提供服务

上面我们说了客户端和服务端分别该怎么做,但是前后端的交互还是不可避免的要通过http协议来传递数据。那在GraphQL下,我们是如何操作的?

首先,还是要保证我们的GraphQL服务器支持http。

以查询举例,我们在服务端需要接受到的是一个query信息,如下面所示:

query {
  games {
    game_name
  }
}
复制代码

我们该怎么把这些数据传递给服务端?

在get方式下:

http://myapi/graphql?query={games{game_name}}
复制代码

查询变量可以作为 JSON 编码的字符串发送到名为 variables 的附加查询参数中。

如果需要传递的参数很多,建议使用post方式传递数据。

当前常用数据交互方式

当前来说,和服务端交互,我们常用的是RESTful API。REST(representational state transfer表象性状态转变)架构指的是URI定位资源,用HTTP动词(GET,POST,DELETE,DETC)描述操作,用HTTP Status Code传递Server的状态信息。REST的主要原则有几个:网络上的所有事物都被抽象为资源,每个资源都有一个唯一的资源标识符,同一个资源具有多种表现形式(xml,json等),对资源的各种操作不会改变资源标识符,所有的操作都是无状态的。

比如,还是说我们的games接口,对于“游戏”我们有增删改查四种操作,怎么定义RESTful的接口?

增加一个游戏,uri: n.ssp.qq.com/api/game 接口类型:POST

删除一个游戏,uri: n.ssp.qq.com/api/game 接口类型:DELETE

修改一个游戏,uri: n.ssp.qq.com/api/game 接口类型:PUT

查找一个游戏,uri: n.ssp.qq.com/api/game 接口类型:GET

RESTful API与GraphQL的对比

Graphql的交互方式

RESTful的交互方式

由上面两个图片的比较,我们可以发现,restful api是多入口的,每一个请求服务端都有特定的返回,如果客户端需要一些新的数据,一定会涉及服务端的修改。graphql的请求是单一入口的,服务端设置好schema之后,可以根据客户端传入的请求信息可预知的返回客户端需要的信息,而不涉及服务端的修改。

GraphQL的优缺点

优点

  • 可读性强,代码即文档
  • 前端友好,数据获取可减少服务端开发
  • 方便数据拼接,减少请求次数
  • 强类型

缺点

  • GraphQL为什么没用火起来:知乎https://www.zhihu.com/question/38596306

学习GraphQL的参考资料

  • GraphQL官方文档
文章分类
前端
文章标签