GraphQL详细信息:什么,如何以及为什么

GraphQL毫不夸张地说,这是IT模式的最后一幕。 如果您还不知道它是什么技术,如何使用它,以及为什么它可能对您有用,那么我们今天为您撰写的文章就为您撰写。 在这里,我们将以爆米花公司API的数据模式实现示例为例,介绍GraphQL的基础知识。 特别是,让我们谈谈数据类型,查询和变异。



什么是GraphQL?


GraphQL是客户端应用程序用来处理数据的一种查询语言。 GraphQL与诸如“方案”之类的概念相关联-这是使您能够组织应用程序中数据的创建,读取,更新和删除的工具(也就是说,在使用数据仓库时,我们具有四个基本功能,通常以缩写CRUD来指代) -创建,读取,更新,删除)。

上面说过,GraphQL用于处理“您的应用程序”中而不是“数据库中”的数据。 事实是GraphQL是一个独立于数据源的系统,也就是说,在哪里组织它的工作都没有关系。

如果您在不了解GraphQL的情况下,以这种技术的名义查看,似乎我们就面临着非常复杂和令人困惑的事情。 该技术的名称带有单词“ Graph”。 这是否意味着要掌握它,您必须学习使用图形数据库? 并且名称包含“ QL”(可以表示“查询语言”,即“查询语言”)这一事实,是否意味着那些想要使用GraphQL的人必须学习一种全新的编程语言?

这些恐惧并不完全合理。 为了让您放心-这是该技术的残酷事实:它只是修饰的GETPOST请求。 通常,虽然GraphQL引入了一些与数据组织及其交互有关的新概念,但该技术的内部机制依赖于良好的旧HTTP请求。

重新思考REST技术


灵活性是使GraphQL技术与著名的REST技术区分开来的原因。 使用REST时,如果一切正确完成,通常会在考虑特定资源或应用程序数据类型的特征的情况下创建端点。

例如,在对端点/api/v1/flavors执行GET请求时/api/v1/flavors预期它将发送如下所示的响应:

 [ {  "id": 1,   "name": "The Lazy Person's Movie Theater",   "description": "That elusive flavor that you begrudgingly carted yourself to the theater for, now in the comfort of your own home, you slob!" }, {   "id": 2,   "name": "What's Wrong With You Caramel",   "description": "You're a crazy person that likes sweet popcorn. Congratulations." }, {   "id": 3,   "name": "Gnarly Chili Lime",   "description": "The kind of popcorn you make when you need a good smack in the face."} ] 

这个答案没有灾难性的错误,但是让我们考虑一下用户界面,或者更确切地说,我们打算如何使用这些数据。

如果我们想在界面中显示一个简单列表,其中仅包含可用类型的爆米花的名称(而没有其他内容),则此列表可能类似于以下所示。


爆米花类型列表

可以看出,这里我们处于困难的境地。 我们可能会决定不使用description字段,但是我们是否会坐下来假装我们没有将此字段发送给客户? 我们还能做什么? 几个月后,当他们会问我们为什么该应用程序对用户如此缓慢时,我们只需要让这个家伙离开,而不再与我们为其创建此应用程序的公司的管理层见面。

实际上,服务器响应客户请求发送不必要的数据这一事实并不完全是我们的错。 REST是一种数据获取机制,可以与餐厅中的服务员进行比较:服务员问游客:“您想要什么?”,而且他并没有特别注意他的意愿,而是对他说:“我会带给我们我们所拥有的” 。

如果我们撇开笑话,那么在实际应用中,这可能会导致出现问题。 例如,我们可以显示有关每种爆米花的各种其他信息,例如价格信息,制造商信息或营养信息(“素食爆米花!”)。 同时,僵化的REST端点使获取特定类型爆米花的特定数据变得非常困难,这导致系统上的负载过高,并且导致的解决方案远非开发人员可以为之骄傲。

GraphQL技术如何改善REST技术用于


对上述情况的表面分析似乎表明我们只是一个小问题。 “向客户发送不必要的数据有什么问题?” 为了了解“不必要的数据”可能成为大问题的程度,请记住GraphQL由Facebook开发。 该公司每秒必须处理数百万个请求。

这是什么意思? 事实如此,每件事都很重要。

GraphQL,如果我们继续与餐厅进行类比,而不是“随身携带”来访者“什么是”,而是带来来访者的确切订单。

我们可以从GraphQL获得响应,该响应集中于使用数据的上下文。 在这种情况下,我们不需要向系统添加“一次性”访问点,执行许多请求或编写多层条件结构。

GraphQL如何工作?


如前所述,GraphQL依靠简单的GETPOST请求将数据传输到客户端并从客户端接收数据。 如果我们更详细地考虑这个想法,事实证明GraphQL中有两种查询类型。 第一种类型包括读取数据的请求,在GraphQL术语中,这些请求简称为查询,指的是首字母缩写CRUD的字母R(读取,读取)。 第二类查询是数据修改请求,在GraphQL中称为变异。 它们与首字母缩写CRUD的轴箱C,U和D有关,也就是说,它们使用它们来创建,创建,更新和删除记录。

所有这些请求和变异都以GETPOST请求的形式发送到GraphQL服务器的URL,例如,URL可能看起来像https://myapp.com/graphql 。 我们将在下面讨论更多。

GraphQL查询


GraphQL查询是代表对服务器接收某些数据的请求的实体。 例如,我们有一个要填充数据的特定用户界面。 对于这些数据,我们转到服务器,执行请求。 使用传统的REST API时,我们的请求采用GET请求的形式。 使用GraphQL时,将使用新的查询语法:

 { flavors {   name } } 

那是JSON吗? 还是JavaScript对象? 两者都不是。 就像我们已经说过的那样,以GraphQL技术的名义,最后两个字母QL表示“查询语言”,即查询语言。 从字面上看,这是一种用于编写数据请求的新语言。 所有这些听起来像是对相当复杂的事物的描述,但实际上,这里没有什么复杂的事物。 让我们分析以上查询:

 { //    ,   . } 

所有请求均以“根请求”开头,在执行请求期间需要获取的内容称为字段。 为了避免混乱,最好将这些实体称为“架构中的查询字段”。 如果您对这样的名字感到难以理解-请稍等-下面我们将详细讨论该计划。 在这里,我们在根查询中请求flavors字段。

 { flavors {   //  ,        flavor. } } 

当请求某个字段时,我们还应指出响应该请求而来的每个对象都需要接收的嵌套字段(即使预期只有一个对象会响应该请求)。

 { flavors {   name } } 

结果如何? 将此类请求发送到GraphQL服务器后,我们将获得格式正确的简洁答案,如下所示:

 { "data": {   "flavors": [     { "name": "The Lazy Person's Movie Theater" },     { "name": "What's Wrong With You Caramel" },     { "name": "Gnarly Chili Lime" }   ] } } 

请注意,没有多余的东西。 为了使内容更清楚,这里执行了另一个请求以获取应用程序另一页上的数据:

 { flavors {   id   name   description } } 

响应此请求,我们得到以下信息:

 { "data": {   "flavors": [     { "id": 1, "name": "The Lazy Person's Movie Theater", description: "That elusive flavor that you begrudgingly carted yourself to the theater for, now in the comfort of your own home, you slob!" },     { "id": 2, "name": "What's Wrong With You Caramel", description: "You're a crazy person that likes sweet popcorn. Congratulations." },     { "id": 3, "name": "Gnarly Chili Lime", description: "A friend told me this would taste good. It didn't. It burned my kernels. I haven't had the heart to tell him." }   ] } } 

如您所见,GraphQL是一项非常强大的技术。 我们转到同一个端点,对请求的答案完全对应于填充执行这些请求的页面所需的内容。

如果只需要获取一个flavor对象,则可以利用GraphQL可以使用参数的事实:

 { flavors(id: "1") {   id   name   description } } 

在这里,我们在代码中严格设置对象的特定标识符( id ),即我们需要的信息,但是在这种情况下,我们可以使用动态标识符:

 query getFlavor($id: ID) { flavors(id: $id) {   id   name   description } } 

在这里,在第一行中,我们给请求指定一个名称(名称是任意选择的,可以用诸如pizza东西来代替getFlavor ,并且该请求将继续运行)并声明该请求所期望的变量。 在这种情况下,假设标量类型ID的标识符( id )将传递给请求(我们将在下面讨论类型)。

无论执行请求时使用的是静态id还是动态id ,对类似请求的响应都将如下所示:

 { "data": {   "flavors": [     { "id": 1, "name": "The Lazy Person's Movie Theater", description: "That elusive flavor that you begrudgingly carted yourself to the theater for, now in the comfort of your own home, you slob!" }   ] } } 

如您所见,一切安排都很方便。 您可能已经开始考虑在自己的项目中使用GraphQL。 而且,尽管我们已经谈论过的内容看起来很棒,但是GraphQL的美丽确实体现在它与嵌套字段一起工作的地方。 假设在我们的方案中,还有一个称为nutrition领域,其中包含有关不同类型的爆米花的营养价值的信息:

 { flavors {   id   name   nutrition {     calories     fat     sodium   } } } 

在我们的数据仓库中,每个flavor对象似乎都将包含一个嵌套的nutrition对象。 但这并非完全正确。 使用GraphQL,您可以在单个查询中组合对独立但相关的数据源的调用,这使您可以接收答案,从而提供处理嵌入式数据的便利,而无需对数据库进行非规范化:

 { "data": {   "flavors": [     {       "id": 1,       "name": "The Lazy Person's Movie Theater",       "nutrition": {         "calories": 500,         "fat": 12,         "sodium": 1000       }     },     ...   ] } } 

这可以显着提高编程器的生产率和系统速度。

到目前为止,我们已经讨论了读取请求。 数据更新请求呢? 使用它们会给我们带来同样的便利吗?

GraphQL突变


当GraphQL查询加载数据时,变异负责对数据进行更改。 可以以基本RPC(远程过程调用)机制的形式使用突变,以解决各种任务,例如将用户数据发送到第三方API。

描述突变时,使用的语法类似于生成查询时使用的语法:

 mutation updateFlavor($id: ID!, $name: String, $description: String) { updateFlavor(id: $id, name: $name, description: $description) {   id   name   description } } 

在这里,我们声明updateFlavor突变,并指定一些变量idnamedescription 。 根据用于描述查询的相同方案,我们使用mutation关键字“绘制”变量字段(根突变),后跟描述突变的名称以及形成相应数据更改请求所需的一组变量。

这些变量包括我们要更改的内容或想要引起的突变。 另请注意,突变后,我们可以请求返回某些字段。

在这种情况下,我们需要在更改记录后获取idnamedescription字段。 当开发诸如乐观接口之类的东西时,这可以派上用场,而无需在更改数据后满足接收更改数据的请求。

设计架构并将其连接到GraphQL服务器


到目前为止,我们已经讨论了GraphQL如何在客户端上工作以及它们如何执行查询。 现在让我们谈谈如何响应这些请求。

▍GraphQL服务器


为了执行GraphQL查询,您需要一个可以向其发送查询的GraphQL服务器。 GraphQL服务器是常规的HTTP服务器(如果使用JavaScript编写,则可以是使用Express或Hapi创建的服务器),并附有GraphQL图。

 import express from 'express' import graphqlHTTP from 'express-graphql' import schema from './schema' const app = express() app.use('/graphql', graphqlHTTP({ schema: schema, graphiql: true })) app.listen(4000) 

所谓“加入”方案,是指一种机制,该机制将通过该方案从客户端收到的请求进行传递并返回其答案。 就像空气过滤器一样,空气通过过滤器进入房间。

“过滤”的过程与客户端发送到服务器的请求或变异相关。 使用与在根查询或架构的根突变中定义的字段相关的功能来解析查询和变异。

上面是使用Express JavaScript库创建的示例HTTP服务器框架。 使用Facebook的express-graphqlgraphqlHTTP函数,我们“附加”了该方案(假定它在单独的文件中描述)并在端口4000上运行服务器。也就是说,谈到该服务器的本地使用的客户端将能够通过以下方式发送请求:地址http://localhost:4000/graphql

▍数据类型和解析器


为了确保GraphQL服务器的运行,您需要准备架构并将其附加到它。

回想一下,我们之前曾讨论过在根查询或根突变中声明字段。

 import gql from 'graphql-tag' import mongodb from '/path/to/mongodb' //  -  . ,  `mongodb`     MongoDB. const schema = { typeDefs: gql`   type Nutrition {     flavorId: ID     calories: Int     fat: Int     sodium: Int   }   type Flavor {     id: ID     name: String     description: String     nutrition: Nutrition   }   type Query {     flavors(id: ID): [Flavor]   }   type Mutation {     updateFlavor(id: ID!, name: String, description: String): Flavor   } `, resolvers: {   Query: {     flavors: (parent, args) => {       // ,  args  ,  { id: '1' }       return mongodb.collection('flavors').find(args).toArray()     },   },   Mutation: {     updateFlavor: (parent, args) => {       // ,  args    { id: '1', name: 'Movie Theater Clone', description: 'Bring the movie theater taste home!' }       //  .       mongodb.collection('flavors').update(args)       //  flavor  .       return mongodb.collection('flavors').findOne(args.id)     },   },   Flavor: {     nutrition: (parent) => {       return mongodb.collection('nutrition').findOne({         flavorId: parent.id,       })     }   }, }, } export default schema 

GraphQL模式中的字段定义由两部分组成-来自类型声明( typeDefs )和resolvertypeDefs包含应用程序中使用的数据的类型声明。 例如,前面我们讨论了从服务器获取flavor对象列表的请求。 为了向我们的服务器发出类似的请求,您需要执行以下三个步骤:

  1. 告诉模式, flavor对象数据的外观(在上面的示例中,它看起来像type Flavor的广告)。
  2. 在“ type Querytype Query的根字段中声明该字段(这是“ type Querytype Queryflavors属性)。
  3. 声明一个resolvers.Query对象识别器函数,该函数根据type Query的根字段中声明的字段编写。

现在让我们注意typeDefs 。 在这里,我们提供有关数据形状的架构信息。 换句话说,我们告诉GraphQL有关相应类型的实体中可能包含的各种属性。

 type Flavor { id: ID name: String description: String nutrition: Nutrition } 

type Flavor声明指示flavor对象可以包含id类型的id字段, String类型的name字段, String类型的description字段和Nutrition类型的Nutrition字段。

nutrition我们在此处使用typeDefs声明的其他类型的名称。 在这里, type Nutrition构建体描述了爆米花的营养数据形式。

请注意以下事实:在本文开始之际,我们正在谈论的是“应用程序”,而不是“数据库”。 在上面的示例中,假设我们有一个数据库,但是应用程序中的数据可以来自任何来源。 它甚至可以是第三方API或静态文件。

就像我们在type Flavor声明中所做的一样,在这里我们指定要包含在nutrition对象中的字段的名称,并使用GraphQL中称为标量数据类型的值作为这些字段(属性)的数据类型。 在撰写本文时,GraphQL支持5种内置标量数据类型

  • Int :有符号的32位整数。
  • Float :带符号的双精度浮点数。
  • String :以UTF-8编码的字符序列。
  • Boolean :布尔值truefalse
  • ID :唯一的标识符,通常用于重复加载对象或作为缓存中的键。 类型ID值以与字符串相同的方式序列化,但是, ID类型具有值的指示通过以下事实强调:该值并非旨在显示给人,而是供程序使用。

除了这些标量类型,我们还可以将属性分配给我们自己定义的类型。 这正是我们通过分配type Flavor类型, Nutrition构造type Flavor描述的nutrition属性所做的。

 type Query { flavors(id: ID): [Flavor] } 

在描述type Query的根类型的type Query结构类型(我们前面提到的“根查询”)中,我们声明了可以请求的字段名称。 通过声明此字段,我们还将与期望返回的数据类型一起指定请求中可能包含的参数。

在此示例中,我们期望可能收到标量类型IDid参数。 响应于这样的请求,期望对象的阵列,其设备类似于Flavor类型的设备。

▍连接查询识别器


, type Query field , , -.

— , GraphQL, , «». resolvers , Query , , flavors , . flavors , type Query .

 typeDefs: gql`…`, resolvers: { Query: {   flavors: (parent, args) => {     // ,  args    { id: '1' }     return mongodb.collection('flavors').find(args).toArray()   }, }, … }, 

- . parent — , , args , . context , . «» ( — , ).

, , . GraphQL « » . , , .

GraphQL , , . JSON-, JSON-, ( GraphQL ).

- flavors MongoDB, args ( ) .find() , , .


-, GraphQL, , , , nutrition . , , Nutrition , , , , flavor . , / .

GraphQL , type Flavor nutrition type Nutrition , . , , flavor .

 typeDefs: gql`   type Nutrition {     flavorId: ID     calories: Int     fat: Int     sodium: Int   }   type Flavor {     […]     nutrition: Nutrition   }   type Query {…}   type Mutation {…} `, resolvers: {   Query: {     flavors: (parent, args) => {…},   },   Mutation: {…},   Flavor: {     nutrition: (parent) => {       return mongodb.collection('nutrition').findOne({         flavorId: parent.id,       })     }   }, }, 

resolvers , , Query , Mutation Flavor . , typeDefs .

Flavors , , nutrition -. , Flavor . , : « , nutrition , type Flavor ».

MongoDB, , parent , -. , parent , , , flavors . , flavor , :

 { flavors {   id   name   nutrition {     calories   } } } 

flavor , flavors , nutrition , parent . , , , MongoDB, parent.id , id flavor , .

parent.id , nutrition flavorId , flavor .


, , . , . type Mutation , , updateFlavor , , .

 type Mutation { updateFlavor(id: ID!, name: String, description: String): Flavor } 

: « , updateFlavor id ID ( , ! , GraphQL , ), name String description String ». , , Flavor ( — , id , name , description , , , nutrition ).

 { typeDefs: gql`…`, resolvers: {   Mutation: {     updateFlavor: (parent, args) => {       // ,  args    { id: '1', name: 'Movie Theater Clone', description: 'Bring the movie theater taste home!' }       //  .       mongodb.collection('flavors').update(         { id: args.id },         {           $set: {             ...args,           },         },       )       //  flavor  .       return mongodb.collection('flavors').findOne(args.id)     },   }, }, } 

- updateFlavor , : , , — , flavor .

, , flavor . ?

, , . , flavor , .

args ? , . , , , 100% , . , , , , , .

GraphQL?


, , , , , GraphQL-API.

, GraphQL , . , . , . , , , GraphQL REST . , , , GraphQL.

▍ ,


, HTTP-, , , , — . GraphQL , , , , ( ).

, , ( — ), GraphQL .

▍ , ,


, , « ». , , , . . GraphQL .

▍ ,


REST API, : , . , -, iOS Android, API . , , , , « » .

, , , HTTP, API (, , ).

▍ GraphQL — ? REST API GraphQL?


不,当然。 . , , GraphQL . GraphQL, . , , , . , , .

, GraphQL , , , . GraphQL , Apollo Relay, .

GraphQL — , , . graphql ( express-graphql , ) — . , GraphQL - . , -, , , , .

总结


, GraphQL , . GraphQL , , , . , , , , GraphQL.

, : GraphQL . GraphQL . , GraphQL, , , , , , , .

— , GraphQL — , , . GraphQL , . , GraphQL — , , , . . , , , , , , GraphQL.

亲爱的读者们! GraphQL — , .

Source: https://habr.com/ru/post/zh-CN445268/


All Articles