当前位置: 首页 > 工具软件 > graphiql > 使用案例 >

什么是GraphQL?

戚修雅
2023-12-01

总览

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

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

  • GraphQL的原理
  • 与REST的比较
  • 如何设计模式
  • 如何设置GraphQL服务器
  • 如何实现查询和变异
  • 和一些其他高级主题

GraphQL在哪里发光?

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

  • 当前的金州勇士阵容中的球员名称是什么?
  • 华盛顿奇才队首发球员的名字,身高和年龄是多少?
  • 哪个现役教练夺冠最多?
  • 教练在哪些球队和哪几年赢得了冠军?
  • 哪个球员获得了MVP最多的奖项?

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

这不是小事。 GraphQL旨在解决这个确切的问题,并且通过单个API端点,它提供了强大的功能,您将很快看到。

GraphQL与REST

在深入研究GraphQL之前,让我们将其与REST(目前是最流行的Web API类型)进行比较。

REST遵循面向资源的模型。 如果我们的资源是球员,教练和团队,那么可能会有以下终点:

  • /玩家
  • / players / <id>
  • /教练
  • / coachs / <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,或者是用户定义的类型。 图的节点是用户定义的类型,边是具有用户定义的类型的属性。

例如,如果“玩家”类型具有“团队”类型的“团队”属性,则意味着每个玩家节点与团队节点之间存在一条边。 所有类型均在描述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!]!
}

感叹号表示该值不能为null。 对于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;

graphiql设置为true可以使我们使用很棒的浏览器内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
        }
      }
    ]
  }
}

因此科比·布莱恩特与湖人队共获得了五个冠军,后者总共获得了16个冠军。 凯文·杜兰特(Kevin Durant)仅与勇士队(Warrior)一起获得了一个冠军,后者总共获得了五个冠军。

GraphQL突变

魔术师约翰逊肯定是场上的魔术师。 但是,没有他的好朋友卡里姆·阿卜杜勒·贾巴尔(Kareem Abdul-Jabbar),他就做不到。 让我们将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。 我预计它将很快成为前端应用。 试试看。 你会喜欢的。

翻译自: https://code.tutsplus.com/tutorials/what-is-graphql--cms-29271

 类似资料: