• 日常搜索
  • 百度一下
  • Google
  • 在线工具
  • 搜转载

什么是GraphQL?

概述

graphql是一个新的、令人高兴的api,用于临时查询和操作。它非常灵活,并提供了许多好处。它特别适合公开以图形和树的形式组织的数据。Facebook 于 2012 年开发了 GraphQL,并于 2015 年将其开源。 

它迅速起飞并成为最热门的技术之一。许多创新公司 在生产中采用并使用了 GraphQL。在本教程中,您将学习: 

  • GraphQL 的原理

  • 与休息相比如何

  • 如何设计模式

  • 如何设置 GraphQL 服务器

  • 如何实现查询和突变 

  • 和一些额外的高级主题

GraphQL 在哪里闪耀?

当您的数据以层次结构或图形组织并且前端希望访问此层次结构或图形的不同子集时,GraphQL 处于最佳状态。考虑一个公开 NBA 的应用程序你有球队、球员、教练、冠军,还有很多关于每一个的信息。以下是一些示例查询:

  • 金州勇士队目前名单上的球员叫什么名字?

  • 华盛顿奇才队首发球员的名字、身高和年龄是多少?

  • 哪个现役教练拥有最多的冠军?

  • 教练在哪支球队和哪一年赢得了他的冠军?

  • 哪位球员获得的MVP奖项最多?

我可以想出数百个这样的查询。想象一下,您必须设计一个 API 以将所有这些查询公开给前端,并且能够在您的用户或产品经理提出新的令人高兴的查询时轻松地使用新的查询类型扩展 API。

这不是微不足道的。GraphQL 旨在解决这个确切的问题,并且它通过一个 API 端点提供了巨大的功能,正如您将看到的那样。

GraphQL 与 rest

在深入了解 GraphQL 的具体细节之前,让我们将其与 REST 进行比较,REST 是目前最流行的 Web API 类型。

REST 遵循面向资源的模型。如果我们的资源是球员、教练和球队,那么可能会有这样的端点:

  • /玩家 

  • /玩家/<id> 

  • /教练

  • /教练/<id> 

  • /团队

  • /teams/<id>

通常没有 id 的端点只返回一个 id 列表,而具有 id 的端点返回一个资源的完整信息。当然,您可以用其他方式设计您的 API(例如,/players 端点也可以返回每个玩家的名称或关于每个玩家的所有信息)。

这种方法在动态环境中的问题在于您要么获取不足(例如,您只获取 id 并需要更多信息),要么过度获取(例如,当您获取每个玩家的完整信息时)只是对名字感兴趣)。 

这些都是难题。当获取不足时,如果您获取 100 个 id,则需要执行 100 次单独的 API 调用来获取每个玩家的信息。过度获取时,您会浪费大量后端时间和网络带宽来准备和传输大量不需要的数据。

有一些方法可以用 REST 来解决它。您可以设计许多定制的端点,每个端点都准确地返回您需要的数据。此解决方案不可扩展。很难保持 API 一致。很难进化它。很难记录和使用它。当这些定制端点之间有很多重叠时,很难维护它。

考虑这些额外的端点:

  • /玩家/名字

  • /players/names_and_championships

  • /团队/首发

另一种方法是保留少量通用端点,但提供大量查询参数。该解决方案避免了多端点问题,但它违背了 REST 模型的原则,并且难以一致地发展和维护。

你可以说 GraphQL 已经将这种方法发挥到了极致。它不考虑定义明确的资源,而是考虑整个领域的子

GraphQL 类型系统

GraphQL 使用由类型和属性组成的类型系统对域进行建模。每个属性都有一个类型。属性类型可以是 GraphQL 提供的基本类型之一,如 ID、String 和 Boolean,也可以是用户定义的类型。图的节点s是用户定义的类型,边是具有用户定义类型的属性。 

例如,如果“玩家”类型具有“团队”属性和“团队”类型,则意味着每个玩家节点到团队节点之间都有一条边。所有类型都在描述 GraphQL 域对象模型的模式中定义。 

这是一个非常简化的 NBA 域模式。球员有一个名字,一个与他关系最密切的球队(是的,我知道球员有时会从一支球队转移到另一支球队),以及该球员赢得的冠军数量。 

球队有一个名字,一系列球员,以及球队赢得的冠军数量。

type Player {
    id: ID
	name: String!
	team: Team!
	championshipCount: Integer!
}

type Team {
	id: ID
	name: String!
	players: [Player!]!
	championshipCount: Integer!
}

还有预定义的入口点。它们是查询、变异和订阅。前端通过入口点与后端通信,并根据需要对其进行定制。

这是一个简单地返回所有玩家的查询:

type Query {
    allPlayers: [Player!]!
}

感叹号表示该值不能为空。在 allPlayers查询的情况下,它可以返回一个空列表,但不能返回 null。此外,这意味着列表中不能有空播放器(因为它包含播放器!)。

设置 GraphQL 服务器

这是一个基于 node-express 的成熟 GraphQL 服务器。它有一个内存中的硬编码数据存储。通常,数据将在数据库中或从另一个服务中获取。数据在此处定义(如果您最喜欢的球队或球员未能成功,请提前道歉):

let data = {
  "allPlayers": {
    "1": {
      "id": "1",
      "name": "Stephen Curry",
      "championshipCount": 2,
      "teamId": "3"
    },
    "2": {
      "id": "2",
      "name": "Michael Jordan",
      "championshipCount": 6,
      "teamId": "1"
    },
    "3": {
      "id": "3",
      "name": "Scottie Pippen",
      "championshipCount": 6,
      "teamId": "1"
    },
    "4": {
      "id": "4",
      "name": "Magic Johnson",
      "championshipCount": 5,
      "teamId": "2"
    },
    "5": {
      "id": "5",
      "name": "Kobe Bryant",
      "championshipCount": 5,
      "teamId": "2"
    },
    "6": {
      "id": "6",
      "name": "Kevin Durant",
      "championshipCount": 1,
      "teamId": "3"
    }
  },

  "allTeams": {
    "1": {
      "id": "1",
      "name": "Chicago Bulls",
      "championshipCount": 6,
      "players": []
    },
    "2": {
      "id": "2",
      "name": "Los Angeles Lakers",
      "championshipCount": 16,
      "players": []
    },
    "3": {
      "id": "3",
      "name": "Golden State Warriors",
      "championshipCount": 5,
      "players": []
    }
  }
}

我使用的库是:

const express = require('express');
const graphqlHTTP = require('express-graphql');
const app = express();
const { buildSchema } = require('graphql');
const _ = require('lodash/core');

这是构建模式的代码。请注意,我在allPlayers根查询中添加了几个变量。

schema = buildSchema(`
  type Player {
    id: ID
    name: String!
    championshipCount: Int!
    team: Team!
  }
  
  type Team {
    id: ID
    name: String!
    championshipCount: Int!
    players: [Player!]!
  }
  
  type Query {
    allPlayers(offset: Int = 0, limit: Int = -1): [Player!]!
  }`

这是关键部分:连接查询并实际提供数据。rootValue对象可能包含多个根。 

在这里,只有allPlayers它从参数中提取偏移量和限制,对所有玩家数据进行切片,然后根据团队 ID 为每个玩家设置团队。这使每个玩家成为一个嵌套对象。

rootValue = {
  allPlayers: (args) => {
    offset = args['offset']
    limit = args['limit']
    r = _.values(data["allPlayers"]).slice(offset)
    if (limit > -1) {
      r = r.slice(0, Math.min(limit, r.length))
    }
    _.forEach(r, (x) => {
      data.allPlayers[x.id].team   = data.allTeams[x.teamId]
    })
    return r
  },
}

最后,这里是graphql端点,传递模式和根值对象:

app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: rootValue,
  graphiql: true
}));

app.listen(3000);

module.exports = app;

设置graphiqltrue使我们能够使用很棒的浏览器内 GraphQL IDE 测试服务器。我强烈推荐它来尝试不同的查询。

使用 GraphQL 的即席查询

一切都准备好了。让我们导航到http://localhost:3000/graphql并享受一些乐趣。

我们可以从简单的开始,只列出玩家名称:

query justNames {
    allPlayers {
    name
  }
}

Output:

{
  "data": {
    "allPlayers": [
      {
        "name": "Stephen Curry"
      },
      {
        "name": "Michael Jordan"
      },
      {
        "name": "Scottie Pippen"
      },
      {
        "name": "Magic Johnson"
      },
      {
        "name": "Kobe Bryant"
      },
      {
        "name": "Kevin Durant"
      }
    ]
  }
}

好吧。我们这里有一些超级巨星。毫无疑问。让我们去做一些更有趣的事情:从偏移量 4 开始,得到 2 名玩家。对于每个球员,返回他们的名字和他们赢得了多少冠军以及他们的球队名称和球队赢得了多少冠军。

query twoPlayers {
    allPlayers(offset: 4, limit: 2) {
    name
    championshipCount
    team {
      name
      championshipCount
    }
  }
}

Output:

{
  "data": {
    "allPlayers": [
      {
        "name": "Kobe Bryant",
        "championshipCount": 5,
        "team": {
          "name": "Los Angeles Lakers",
          "championshipCount": 16
        }
      },
      {
        "name": "Kevin Durant",
        "championshipCount": 1,
        "team": {
          "name": "Golden State Warriors",
          "championshipCount": 5
        }
      }
    ]
  }
}

所以科比·布莱恩特随湖人队赢得了5次总冠军,湖人队总共获得了16次总冠军。凯文杜兰特在勇士队只获得了一个总冠军,而勇士队总共获得了五次总冠军。

GraphQL 突变

魔术师约翰逊无疑是场上的魔术师。但如果没有他的朋友卡里姆·阿卜杜勒-贾巴尔,他就无法做到这一点。让我们将 Kareem 添加到我们的数据库中。我们可以定义 GraphQL 突变来执行诸如添加、更新和删除图表中的数据之类的操作。

首先,让我们在模式中添加一个突变类型。它看起来有点像函数签名:

type Mutation {
    createPlayer(name: String, 
                 championshipCount: Int, 
                 teamId: String): Player
}

然后,我们需要实现它并将其添加到根值中。该实现只需获取查询提供的参数并将新对象添加到data['allPlayers']它还确保正确设置团队。最后,它返回新玩家。

  createPlayer: (args) => {
    id = (_.values(data['allPlayers']).length + 1).toString()
    args['id'] = id
    args['team'] = data['allTeams'][args['teamId']]
    data['allPlayers'][id] = args
    return data['allPlayers'][id]
  },

要实际添加 Kareem,我们可以调用突变并查询返回的玩家:

mutation addKareem {
  createPlayer(name: "Kareem Abdul-Jabbar", 
               championshipCount: 6, 
               teamId: "2") {
    name
    championshipCount
    team {
      name
    }
  }
}

Output:

{
  "data": {
    "createPlayer": {
      "name": "Kareem Abdul-Jabbar",
      "championshipCount": 6,
      "team": {
        "name": "Los Angeles Lakers"
      }
    }
  }
}

这是一个关于突变的黑暗小秘密......它们实际上与查询完全相同。您可以在查询中修改数据,也可以只从突变中返回数据。GraphQL 不会窥探您的代码。查询和突变都可以接受参数并返回数据。它更像是语法糖,使您的模式更具人类可读性。

高级主题

订阅

订阅是 GraphQL 的另一个杀手级功能。使用订阅,客户端可以订阅在服务器状态更改时将触发的事件。订阅是在后期引入的,并由不同的框架以不同的方式实现。

验证

GraphQL 将根据模式验证每个查询或突变。当输入数据具有复杂的形状时,这是一个巨大的胜利。您不必编写烦人且脆弱的验证代码。GraphQL 会为您处理好它。 

模式自省

您可以检查和查询当前架构本身。这为您提供了动态发现模式的元能力。这是一个返回所有类型名称及其描述的查询:

query q {
  __schema {
    types {
      name
      description            
    }
  }

结论

GraphQL 是一种令人高兴的新 API 技术,它提供了许多优于 REST API 的优势。它背后有一个充满活力的社区,更不用说 Facebook。我预测它很快就会成为前端的主食。试试看。你会喜欢的。 


文章目录
  • 概述
  • GraphQL 在哪里闪耀?
  • GraphQL 与 rest
  • GraphQL 类型系统
  • 设置 GraphQL 服务器
  • 使用 GraphQL 的即席查询
  • GraphQL 突变
  • 高级主题
    • 订阅
    • 验证
    • 模式自省
  • 结论