写点什么

推开 GraphQL 大门

作者:梁龙先森
  • 2021 年 12 月 08 日
  • 本文字数:3471 字

    阅读完需:约 11 分钟

推开GraphQL大门

一、什么是 GraphQL

官网上说,GraphQL 是一种用于 API 的查询语言。习惯了 REST API,刚开始对这个定义很不理解。再看它的全称:Graph Query Language,图状数据查询语言,这会大体能了解它对图状数据查询应该存在明显的优势。那么 GraphQL 怎么使用呢?如下图所示:

同时,可以看出返回的数据跟请求描述的数据结构是相近的,那么它是否是由客户端自行定义如何去获取 GraphQL API 定义的数据结构?答案:是的,这就是 GraphQL。它存在以下几点特性:

  1. 它的接口表述能力够高,查询语法跟查询结果相近,能准确获取数据

  2. GraphQL API 基于类型和字段的方式进行组织,而非入口端点。能够通过单一入口获取所有数据能力。

  3. API 演进无需划分版本,给 GraphQL API 添加字段和类型无需影响现有查询,老旧字段废弃隐藏即可。

  4. 获取多个资源,只用一个请求。


https://segmentfault.com/a/1190000014131950

https://www.jianshu.com/p/da1260b95faf

https://www.jianshu.com/p/03a7d390375d

https://blog.csdn.net/ctrip_tech/article/details/98805156

二、GraphQL 应用场景

GraphQL 主要应用场景是在 API 网关,或者说在 BFF 层(Backend For Frontend),用于做服务的聚合。为什么这么说呢?

  • 对于前端多端的场景,可能各端对同一份数据的形态和结构都存在差异,这很大程度加大后端人员的工作量,因此需要减少各端对 API 团队的依赖。

  • 微服务架构下,服务通常根据业务做了领域的细分,再通过搭载 BFF 层对服务数据做聚合管理给前端提供数据,这是 REST 服务常见的操作。但后端人员一方面要理解业务领域模型,同时也要理解页面展示数据需求,当业务越来越庞大,BFF 层聚合能力自身也容易成为瓶颈。

那么,让前端接管 BFF 层做数据的消费,是能够减少数据展示沟通上的成本。但 REST 架构面对业务迭代复杂度过大,版本迭代等问题造成的瓶颈仍需要技术方案来解决。而 GraphQL 恰好是另一种解决思路。举个现实案例,有这么个场景,你需要展示商品详情和商品对应的用户评论,那么在 PC 端你可以不考虑性能问题,让商品详情和商品评论在同一个接口直接返回;但倘若有天接口需要在移动端进行使用,同一个接口返回所有数据过大,会影响性能,这时候你需要将一个接口拆成 2 个接口来解决。这是 REST API 存在的局限性。

三、Schema 和类型系统

GraphQL API 遵循 GraphQL Schema,协议定义了 API 所能支持的操作,包括输入参数和返回内容。该协议是属于强类型的,跟 TypeScript 有些类似。下面看下它的基本类型系统。学过 JavaScript,我们知道它存在基本数据类型和引用类型。GraphQL 的类型也无异于此,分为 Scalar Type(标量类型)和 Object Type(对象类型)。

1. Scalar Type
  1. 内建的标量类型有:StringIntFloatBooleanEnum,同时它也支持通过 Scalar 来声明一个新的标量。

  2. 标量是整个类型系统中最小的颗粒。

2. Object Type

对象类型用来表述一些复杂抽象的数据模型,比如用户、商品。

type User { id: ID name: String age: Int}
复制代码

上面就声明了一个 User 类型,它存在 3 个字段(Fields),且都对应着标量类型。当然字段也可以对应复杂的对象模型,比如下面:

type School {  name: String	address: String}
type User { id: ID name: String age: Int school: School}
复制代码

总之,类型定义可以按照你所需的进行定义它们直接的关联关系。

3. Query 和 Mutation

我们 Scheme 中除了普通对象类型,还存在 QueryMutation 两个特殊类型,它们定义了查询的入口,除此之外他们跟普通对象类型别无二致。

// 协议定义schema {  query: Query  mutation: Mutation}
type Query { users(): [User!]!}
type Mutation { addUser(): User!}
// 请求数据query { users { name }}
// 返回数据{ "data": { "users": [ {"name": "**"} ] }}
复制代码
4. Type Modifier

GraphQL 存在 List(用"[]'表示)和 Required(用'!'表示)两种修饰符,一种表示为数组,另一种表示为必须项,不能为空。

type User {  // 表示id不能为空  id: ID!   // 表示字符串列表,列表可为空,内部元素也可为空  hobits: [String]  // 列表项必填,内部元素可以为空  names: [String]!  // 列表项必填,内部元素必填  friends: [String!]!}
复制代码

了解完 GraphQL 的 Schema 和类型系统,下面用 Node.js 搭建一个 GraphQL 服务。

四、搭建 GraphQL 服务

使用 JavaScript 搭建 GraphQL 服务的方式有很多种,社区也提供了很多支持方案,比如:

  1. 直接使用 graphql 库的 graphqlbuildSchema 函数可以快速实现

  2. 借助 apollo-server-expressexpress,这是我司目前采用的

  3. 通过 express express-graphql graphql 三个库配合实现

当然,能够搭建 GraphQL 服务的实现的方式不仅以上 3 种,下面我们介绍另外一种搭建的方案:

通过graphql-tools 和 express-graphql搭建,前者负责写协议和解析代码,后者将其连接到 web 服务器。看看 graphql-tools 提供的能力:

  1. 生成的协议完全支持解析器、接口、联合和自定义标量,与 GraphQL.js 完全兼容

  2. 使用细粒度的每种类型模拟 GraphQL API

  3. 自动将多个协议拼接成一个更大的 API

也因为它生成的协议与 GraphQL.js 完全兼容,所以生成的协议其实也是可以在 Apollo GraphQL 中使用。下面开始搭建环境。

1. 创建工程
// 1. 创建文件mkdir node-graphql && cd node-graphql// 2. 初始化配置文件 package.jsonnpm init
复制代码
2. 安装依赖

服务端框架采用 express,并且采用 typescript 语言编写方便进行类型定义,同时安装对应的graphql 包和它对应的 ts 类型定义。

{   "dependencies": {    "express": "^4.17.1",    "express-graphql": "^0.12.0",    "graphql": "^16.0.1",    "graphql-tools": "^8.2.0"  },  "devDependencies": {    "@types/express": "^4.17.13",    "@types/express-graphql": "^0.9.0",    "@types/graphql": "^14.5.0",    "typescript": "^4.5.2"  }}
复制代码
3. typescript 编译配置

在项目根目录创建 tsconfig.json 配置文件,配置编译输出目录为 build,同时创建 app.ts 入口文件。

{  "compilerOptions": {    "target": "ES2016",    "module": "commonjs",    "outDir": "./build",    "strict": true,    "esModuleInterop": true  }}
复制代码
4. 配置 npm scripts

配置完成,便可以通过执行 npm run start 进行项目启动。在 start 命令中,存在 2 条指令:npm run tsc:该命令是将 typescript 编译成 js 代码。node ./build/app.js:该命令是用 node 执行 js 脚本文件。这两条命令行是通过 "&&" 操作符连接起来,表示两条命令是串行的关系,只有前面的命令执行结束才会执行后面的命令。那如果需要多条命令并行执行呢?把命令行连接符号改为 "&" 即可。

{  "scripts": {    "tsc": "tsc",    "start": "npm run tsc && node ./build/app.js"  },}
复制代码
5. 书写 Hello 服务

在 app.ts 文件中

import express from "express";const { graphqlHTTP } = require("express-graphql");import { makeExecutableSchema } from "@graphql-tools/schema";
const app: express.Application = express();const port = 3000;
// 定义类型let typeDefs: any = [ ` type Query { hello: String } type Mutation { hello(message: String) : String }`,];
let helloMessage: String = "World!";
// 将类型定义映射到对应的解析器上let resolvers = { Query: { hello: () => helloMessage, }, Mutation: { hello: (_: any, helloData: any) => { helloMessage = helloData.message; return helloMessage; }, },};
// 当访问路径为:/graphql,会执行中间间app.use( "/graphql", graphqlHTTP({ schema: makeExecutableSchema({ typeDefs, resolvers }), graphiql: true, }));// 监听端口app.listen(port, () => console.log(`Node Graphql API listening on port ${port}!`));
复制代码
6. 解析器 Resolvers

协议上定义了 Query 和 Mutation 实际上只进行了一半,实际的核心在于解析器 Resolvers,业务逻辑基本都在这里处理和构造,比如与数据库等交互,它代表着获取数据的真实逻辑。它的命名约定与定义的查询命名一致,因为当接收到一个查询时,会递归解析查询语句,并尝试用对应类型的解析器去获取数据,并将获取的数据给当前的 field。

7. 浏览器访问

浏览器访问服务,在左边输入框输入查询语句,然后点击运行按钮,便返回接口数据了。

关于更复杂的例子,建议自身去尝试。

五、总结

针对 GraphQL 了解它是什么,应用场景在哪,掌握基本用法并且能够搭建环境,至于更复杂的业务场景和实现,实战中去践行吧。

用户头像

梁龙先森

关注

无情的写作机器 2018.03.17 加入

vite原理/微前端/性能监控方案...,正在来的路上...

评论

发布
暂无评论
推开GraphQL大门