GraphQL 入门与实践

概述

GraphQL 是一种新的 API 标准,由 Facebook 开发并开源。

Facebook 在 2012 年就在他们移动端应用中使用上了 GraphQL,第一次公开发布是在 2015 年的 React Conf 上。其实不仅 Facebook,其他公司也有在探索相关类似的技术,Netflix 也曾推出他们的方案 Falcor,Coursera 在 Facebook 推出 GraphQL 后,取消了相关研发,直接使用 GraphQL。

为什么会有这么多公司都在研发相关技术,让我们先看 GraphQL 是如何使用的,再看看它和现在的 RESTful api 的对比。

GraphQL 入门示例

我们将使用 apollo-server 来创建 GraphQL 示例,或者你也可以使用 express-graphql。示例来源 Apollo 官方,地址。

一个 GraphQL 服务是通过定义类型和类型上的字段来创建的,所以我们先定义类型和字段

const { ApolloServer, gql } = require('apollo-server');

// 定义了一个 Book 类型,后续我们可以在 Query 中使用
const typeDefs = gql(`# 定义了一个 Book 类型,后续我们可以在 Query 中使用type Book {title: Stringauthor: String} # Query 类型是特殊,它列出所有客户端可查询的字段type Query {books: [Book]}
`) 

接着我们需要处理对应的字段解析函数,这边我们需要处理 books 是怎么获取数据的,最后返回一定是一个 Book 类型数组。

const books = [{title: 'The Awakening',author: 'Kate Chopin',
}, {title: 'City of Glass',author: 'Paul Auster',
}];

const resolvers = {Query: {books: () => books,}
}; 

最后创建 ApolloServer 并启动,一个 GraphQL 服务就跑起来了。

const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {console.log(` Server ready at ${url}`);
}); 

本地启动服务后,你可以访问 http://localhost:4000/ 并进入 GraphQL playground 来体验(或者使用 apollo 官方 demo 体验)。根据定义的类型和类型上的字段,我们编写对应的查询语句即可获取到想要的数据。你也可以试着只返回 book 的 title 试试。

GraphQL 入门与实践_第1张图片

从上面例子就可以体验到 GraphQL 的使用并不复杂,并且可以查询自己想要的数据。

GraphQL 优点和缺点

Facebook 设计出 GraphQL 是为了处理 RESTful API 的一些局限性, 所以我们先看下它的优点,再看 GraphQL 自己的局限性。

优点

1.Overfetching & Underfetching

Overfetching 很好理解,平时我们用 RESTful API 请求数据的时候,接口往往会返回很多字段,有些字段是我们不需要的。而 GraphQL 可以指定要返回什么数据,就不会出现数据冗余。Fackbook 最先也是在移动端应用上使用 GraphQL 的,没有数据冗余对于移动端的访问来说可以更快的展示页面。

Underfetching 指请求的返回的数据信息不够,这样导致我们需要发多个请求去获取数据。比如一个页面需要显示用户信息,包括基本信息和用户文章。这个需求对于 RESTful API 来说通常需要两个接口去获取,一个获取基本信息,一个获取用户的文章。但是对于 GraphQL 来说,一个请求就搞定,指定基本信息和文章的字段,接口自然返回相关数据。

2.快速产品迭代

RESTful API 在产品需求迭代中,很可能发生接口数量和接口 url 的变更,这些变更的影响范围可能会比较大;而 GraphQL 的接口只有一个,前端就只需关心自己想要的数据,可以更快速的进行产品迭代。

3.灵活而强类型的 Schema

GraphQL 是强类型的,查询基于字段及其关联的数据类型。如果 GraphQL 查询中存在类型不匹配,则服务端将返回明确且有用的错误消息。同时通过 Schema,前端和后端的工作可以各自开发,最后在联调。

4.内省分析

GraphQL 支持通过内省机制可以知道它支持哪些查询,这对于 GraphQL IDE 工具 非常友好,我们在上面的例子中就可以体验到。

缺点

1.HTTP 缓存

由于 GraphQL 在 HTTP 上使用方式为单个端点中的 POST 请求,POST 请求的数据结构又是不固定的,所以无法在 HTTP 层上做数据缓存。社区是有提供一些客户端级别缓存方案,比如 Apollo Client,但是会增加开发成本。

2.HTTP Status

正常情况下 GraphQL 只会返回 Status Code 200,无论当前数据请求是成功或失败,这样传统方法的 HTTP 状态判断和逻辑就无法使用,虽然开发者可以自定义一套错误处理逻辑,但也增加了复杂度。

返回 200 具体错误在 errors 中

{"errors": [{"message": "Field "name" must not have a selection since type "String" has no subfields.","locations": [ {"line": 31,"column": 101 }]}
 ]
} 

3.不可预测的执行

GraphQL 的本质是你可以查询组合你想要的任何字段,但这种灵活性不是免费的。有一些问题值得了解,例如性能和 N+1 查询。

GraphQL 使用 resolves 去获取字段的数据,每个字段都有执行 resolves,这就会对性能有一定的影响。同时如果查询中存在很深的嵌套,服务端需要一个机制去阻止这个性能昂贵的查询。

4.处理文件上传

GraphQL规范中没有关于文件上传的内容,并且不接受文件类型参数

Learn GraphQL

这个主要介绍 GraphQL 查询能力,对于服务端的 Schema 定义和实现,有兴趣的同学可以根据这个教程进行学习,How to GraphQL

  • 参数

GraphQL 支持在请求时传递参数,每个字段都可以设置参数(服务端支持的情况下)。

{human(id: "1000") {nameheight(unit: "cm")}
} 
  • 别名

当你想通过不同参数来查询相同字段就需要使用别名功能,比如下面的 hero 查询,在不使用别名的情况下你就无法构造出这个请求。

{empireHero: hero(episode: EMPIRE) {name}jediHero: hero(episode: JEDI) {name}
} 
  • 变量

查询语句支持变量,使用 $ 符定义变量,并在发起请求时传入变量。

# { "graphiql": true, "variables": { "episode": JEDI2 } }
query HeroNameAndFriends($episode: Episode = "JEDI") {hero(episode: $episode) {namefriends {name}}
} 
  • 片段

片段使你能够组织一组字段,然后在需要它们的地方引入,同时片段内也可以使用变量

query HeroComparison($first: Int = 3) {leftComparison: hero(episode: EMPIRE) {...comparisonFields}rightComparison: hero(episode: JEDI) {...comparisonFields}
}

# 定义了一个片段,并在上面查询中使用
fragment comparisonFields on Character {name# 使用了 HeroComparison 的变量friendsConnection(first: $first) {totalCountedges {node {name}}}
} 
  • 内联片段

你查询的字段返回的是接口或者联合类型,那么你可能需要使用内联片段来取出下层具体类型的数据

## hero 可能是 Droid 或者 Human 类型,各自类型返回各自数据
query HeroForEpisode($ep: Episode!) {hero(episode: $ep) {name... on Droid {primaryFunction}... on Human {height}}
}


# 你也可以通过片段方式来实现
query GetMyProfile {me {nameprofilePicture...HostProfileFields...GuestProfileFields}
}
fragment HostProfileFields on Host {profileDescription
}
fragment GuestProfileFields on Guest {funds
} 
  • 指令

一个指令可以附着在字段或者片段包含的字段上,根据指令就会有不同的返回数据

# @include(if: Boolean) 仅在参数为 true 时,包含此字段。
query Hero($episode: Episode, $withFriends: Boolean!) {hero(episode: $episode) {namefriends @include(if: $withFriends) {name}}
}


# @skip(if: Boolean) 如果参数为 true,跳过此字段。
query Hero($episode: Episode, $withFriends: Boolean!) {hero(episode: $episode) {namefriends @skip(if: $withFriends) {name}}
} 
  • Mutation

变更并查询这个字段的新值,类比 RESTful API 中的 POST、PUT 等方式请求

mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {createReview(episode: $ep, review: $review) {starscommentary}
} 
  • 元字段

GraphQL 允许你在查询的任何位置请求 __typename,它是一个元字段,可以获得那个位置的对象类型名称

# 在不适用 __typename 的情况下,你就无法知道这个 name 的字段是那个类型的
{search(text: "an") {__typename... on Human {name}... on Droid {name}... on Starship {name}}
}


{"data": {"search": [{"__typename": "Human","name": "Han Solo"},{"__typename": "Human","name": "Leia Organa"},{"__typename": "Starship","name": "TIE Advanced x1"}]}
} 

总结

RESTful 和 GraphQL 都是数据传输解决方案,GraphQL 可以显著的节省网络传输资源,在带宽紧张的环境中(例如移动端),这将发挥巨大的作用。尽管 GraphQL 相比 REST 有很多显著的优点和升级,但在真实场景中,它并不一定是最适合你的实现。

总结来说,如果你希望做的应用追求简单而敏捷,且没有什么特殊考量,那就没什么必要使用 GraphQL,RESTful 可靠、经济、不易出错;反而言之,如果应用的关键点在于组织复杂数据逻辑,请求存在较多 Overfetching、Underfetching 的情况,或者对于网络环境敏感,可以尝试 GraphQL。

你可能感兴趣的:(前端,http,javascript,vue.js)