GraphQL开发整理

【后续补充Apollo相关】

前言

REST 的 API 配合JSON格式的数据交换,使得前后端分离、数据交互变得非常容易,而且也已经成为了目前Web领域最受欢迎的软件架构设计模式。

REST的缺点:

1、大量滥用,导致大量相似度很高(具有重复性)的API冗余;
2、对于前端而言:REST API粒度较粗,难以一次性符合前端的数据要求,前端需要分多次请求接口数据。增加了前端人员的工作量。
3、对于后端而言:前端需要的数据往往在不同的地方具有相似性,但却又不同,比如针对同样的用户信息,有的地方只需要用户简要信息(比如头像、昵称),有些地方需要详细的信息,这就需要开发不同的接口来满足这些需求。当这样的相似但又不同的地方多的时候,就需要开发更多的接口来满足前端的需要。增加了后端开发人员的工作量和重复度。

当遇到当前端需求变化,涉及到改动旧需求时,有两种手段:

  • 加法
  • 减法

【其实用到的数据大多都是来自于同一个DO或者DTO,不过是在REST接口组装数据时,用不同的VO来封装不同字段,或者,使用同样的VO,组装数据时做删减。】

那么有没有一种方案或者框架,可以使得在用到同一个领域模型(DO或者DTO)的数据时,前端对于这个模型的数据字段需求的改动,后端可以根据前端的改动和需要,自动适配,自动组装需要的字段,返回给前端呢?

针 对 这 痛 点 , 出 现 了 G r a p h Q L \color{red}{针对这痛点,出现了GraphQL} GraphQL

GraphQL 官网

GraphQL简介

  • GraphQL是一种新的API标准,它提供了一种比REST更有效、更强大和更灵活的替代方案。
  • 它是由Facebook开发并开源的,现在由来自世界各地的公司和个人组成的大型社区维护。
  • GraphQL本质上是一种基于api的查询语言,现在大多数应用程序都需要从服务器中获取数据,这些数据存储可能存储在数据库中,API的职责是提供与应用程序需求相匹配的存储数据的接口。
  • 它是数据库无关的,而且可以在使用API的任何环境中有效使用,我们可以理解为GraphQL是基于API之上的一层封装,目的是为了更好,更灵活的适用于业务的需求变化。
GraphQLREST API
声明式数据获取,使得接口数据精确返回,数据查询流程简洁,照顾了客户端的灵活性接口灵活性差、接口操作流程繁琐
一个服务仅暴露一个 GraphQL 层,消除了服务器对数据格式的硬性规定,客户端按需请求数据,可进行单独维护和改进客户端拓展功能时要不断编写新接口(依赖于服务端)
传输层无关、数据库技术无关使得 GraphQL 有更加灵活的技术栈选择,能够实现在网络协议层面优化应用REST API 基于HTTP协议,不能灵活选择网络协议

GraphQL获取数据

1、首先要设计数据模型,用来描述数据对象,它的作用可以看做是VO,用于告知GraphQL如何来描述定义的数据,为下一步查询返回做准备;
2、前端使用模式查询语言(Schema)来描述需要请求的数据对象类型和具体需要的字段(称之为声明式数据获取);
3、后端GraphQL通过前端传过来的请求,根据需要,自动组装数据字段,返回给前端。

在这里插入图片描述

Schema 和类型

Query 、Mutation and Subscription

GraphQL对数据支持的操作有:

  • 查询(Query):获取数据的基本查询。
  • 变更(Mutation):支持对数据的增删改等操作。
  • 订阅(Subscription):用于监听数据变动、并靠websocket等协议推送变动的消息给对方。

Schema(图表模式)【 核 心 概 念 \color{red}{核心概念}

引入 schema 的原因

因为一个 GraphQL 查询的结构和结果非常相似,因此即便不知道服务器的情况,你也能预测查询会返回什么结果。但是一个关于我们所需要的数据的确切描述依然很有意义,我们能选择什么字段?服务器会返回哪种对象?这些对象下有哪些字段可用?

每一个 GraphQL 服务都会定义一套类型,用以描述你可能从那个服务查询到的数据。每当查询到来,服务器就会根据 schema 验证并执行查询。

想要描述数据,就必须离不开数据类型的定义。所以GraphQL设计了一套Schema模式(可以理解为语法),其中最重要的就是数据类型的定义和支持。
------ 类 型 ( T y p e ) \color{red}{类型(Type)} Type就是模式(Schema)最核心的东西

什么是类型?

  • 对于数据模型的抽象是通过类型(Type)来描述的,每一个类型有若干字段(Field)组成,每个字段又分别指向某个类型(Type)。这很像Java、C#中的类(Class)。
  • GraphQL的Type简单可以分为两种,一种叫做Object Type(对象类型),另一种叫做Scalar Type(标量类型)。

那么就分别来介绍下两种类型。

标量类型(Scalar Type)

一个对象类型有自己的名字和字段,而某些时候,这些字段必然会解析到具体数据。这就是标量类型的来源:它们表示对应 GraphQL 查询的叶子节点。

下列查询中,name 和 appearsIn 字段将解析到标量类型:

查询

  hero {
    name
    appearsIn
  }
}

响应

{
  "data": {
    "hero": {
      "name": "R2-D2",
      "appearsIn": [
        "NEWHOPE",
        "EMPIRE",
        "JEDI"
      ]
    }
  }
}

我们知道这些字段没有任何次级字段 —— 因为让它们是查询的叶子节点。

GraphQL 自带一组默认标量类型:

  • Int:有符号 32 位整数。
  • Float:有符号双精度浮点值。
  • String:UTF‐8 字符序列。
  • Boolean:true 或者 false。
  • ID:ID 标量类型表示一个唯一标识符,通常用以重新获取对象或者作为缓存中的键。ID 类型使用和 String 一样的方式序列化;然而将其定义为 ID 意味着并不需要人类可读型。
    大部分的 GraphQL 服务实现中,都有自定义标量类型的方式。例如,我们可以定义一个 Date 类型:
scalar Date

然后就取决于我们的实现中如何定义将其序列化、反序列化和验证。例如,你可以指定 Date 类型应该总是被序列化成整型时间戳,而客户端应该知道去要求任何 date 字段都是这个格式。

对象类型(Object Type)

仅有标量类型是不能满足复杂抽象数据模型的需要,这时候我们可以使用对象类型。
通过对象模型来构建GraphQL中关于一个数据模型的形状,同时还可以声明各个模型之间的内在关联(一对多、一对一或多对多)。

一个 GraphQL schema 中的最基本的组件是对象类型,它就表示你可以从服务上获取到什么类型的对象,以及这个对象有什么字段。使用 GraphQL schema language,我们可以这样表示它:

type Character {
  name: String!
  appearsIn: [Episode!]!
}

虽然这语言可读性相当好,但我们还是一起看看其用语,以便我们可以有些共通的词汇:

  • Character 是一个 GraphQL 对象类型,表示其是一个拥有一些字段的类型。你的 schema 中的大多数类型都会是对象类型。
  • nameappearsInCharacter 类型上的字段。这意味着在一个操作 Character 类型的 GraphQL 查询中的任何部分,都只能出现 nameappearsIn 字段。
  • String 是内置的标量类型之一 —— 标量类型是解析到单个标量对象的类型,无法在查询中对它进行次级选择。后面我们将细述标量类型。
  • String! 表示这个字段是非空的,GraphQL 服务保证当你查询这个字段后总会给你返回一个值。在类型语言里面,我们用一个感叹号来表示这个特性。
  • [Episode!]! 表示一个 Episode 数组。因为它也是非空的,所以当你查询 appearsIn 字段的时候,你也总能得到一个数组(零个或者多个元素)。且由于 Episode! 也是非空的,你总是可以预期到数组中的每个项目都是一个 Episode 对象。

现在你知道一个 GraphQL 对象类型看上去是怎样,也知道如何阅读基础的 GraphQL 类型语言了。

列表和非空(Lists and Non-Null)

那么,类型系统仅仅只有类型定义是不够的,我们还需要对类型进行更广泛性的描述。
类型修饰符(Type Modifier)就是用来修饰类型,以达到额外的数据类型要求控制。

比如:

  • 列表:[Type]
  • 非空:Type!
  • 列表非空:[Type]!
  • 非空列表,列表内容类型非空:[Type!]!
    在描述数据模型(模式Schema)时,就可以对字段施加限制条件。 实 例 可 按 参 考 上 方 t y p e C h a r a c t e r \color{red}{实例可按参考上方 type Character} typeCharacter
其他类型(接口,联合,输入)

除了上面的,Graphql还有一些其他类型来更好的引入面向对象的设计思想:

接口类型(Interfaces):

其他对象类型实现接口必须包含接口所有的字段,并具有相同的类型修饰符,才算实现接口。

例如,你可以用一个 Character 接口用以表示《星球大战》三部曲中的任何角色:

interface Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
}

这意味着任何实现 Character 的类型都要具有这些字段,并有对应参数和返回类型。

例如,这里有一些可能实现了 Character 的类型:

type Human implements Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
  starships: [Starship]
  totalCredits: Int
}

type Droid implements Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
  primaryFunction: String
}
联合类型(Union Types):

联合类型和接口十分相似,但是它并不指定类型之间的任何共同字段。几个对象类型共用一个联合类型。

union SearchResult = Human | Droid | Starship

在我们的schema中,任何返回一个 SearchResult 类型的地方,都可能得到一个 Human、Droid 或者 Starship。注意,联合类型的成员需要是具体对象类型;你不能使用接口或者其他联合类型来创造一个联合类型。

这时候,如果你需要查询一个返回 SearchResult 联合类型的字段,那么你得使用条件片段才能查询任意字段。
请求

{
  search(text: "an") {
    __typename
    ... on Human {
      name
      height
    }
    ... on Droid {
      name
      primaryFunction
    }
    ... on Starship {
      name
      length
    }
  }
}

响应

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

_typename 字段解析为 String,它允许你在客户端区分不同的数据类型。

此外,在这种情况下,由于Human Droid 共享一个公共接口(Character),你可以在一个地方查询它们的公共字段,而不必在多个类型中重复相同的字段:

{
  search(text: "an") {
    __typename
    ... on Character {
      name
    }
    ... on Human {
      height
    }
    ... on Droid {
      primaryFunction
    }
    ... on Starship {
      name
      length
    }
  }
}

注意name 仍然需要指定在Starship 上,否则它不会出现在结果中,因为 Starship 并不是一个Character

输入类型(Input Types):

更新数据时有用,与常规对象只有关键字修饰不一样,常规对象时 type 修饰,输入类型是 input 修饰。
比如定义了一个输入类型:

input ReviewInput {
  stars: Int!
  commentary: String
}

你可以像这样在变更(mutation)中使用输入对象类型:

mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}

VARIABLES

{
  "ep": "JEDI",
  "review": {
    "stars": 5,
    "commentary": "This is a great movie!"
  }
}

响应

{
  "data": {
    "createReview": {
      "stars": 5,
      "commentary": "This is a great movie!"
    }
  }
}

客户端响应数据问题

数据约定:
格式 {
m e s a g e \color{red}{mesage } mesage 附带的信息
s t a t u s \color{red}{status } status 值 -1,0,1,2,3,4,100大于0存在异常 其中100 代表graphql抛出的异常状态
c o d e \color{red}{code } code 值String 在status ==100 下 code 有以下值

  • Graphql-500 服务器内部错误或者远程接口500
  • Graphql-501 前端的查询语法错误
  • Graphql-502 接口数据格式不正确
  • Graphql-503 接口访问失败
  • Graphql-504 服务器状态码非正常状态 输出服务器返回的信息
  • Graphql-505 参数格式不对
  • Graphql-UnKnown 未知错误
    d a t a \color{red}{data } data 数据内容
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
GraphQL是一种用于构建API的查询语言和运行时的开发工具。相比于传统的REST架构,GraphQL通过提供更清晰、更高效的数据查询和响应机制,极大地提升了开发人员的效率和灵活性。 学习GraphQL需要掌握以下几个方面。首先,要了解GraphQL的核心概念,比如查询、变异、订阅等,以及它们在数据查询和响应过程中的作用。其次,需要学会使用GraphQL的查询语句来获取、筛选和组合数据,包括字段选择、参数传递和嵌套查询等。此外,还需要熟悉GraphQL Schema语言,它定义了可用字段、类型和操作,以及它们之间的关系。掌握Schema语言对于构建和维护GraphQL API非常重要。 在学习过程中,可以使用一些流行的GraphQL库和框架,如Apollo和Relay,它们提供了丰富的工具和功能来简化GraphQL开发。此外,阅读官方的GraphQL规范和文档,参与社区讨论和实践,也是学习的重要途径。 学会GraphQL后,你可以享受到许多好处。首先,GraphQL提供了更高效、精确的数据查询和响应机制,减少了网络传输的数据量,提高了前端性能。其次,GraphQL允许客户端指定需要的数据,避免了传统REST API中的过度获取或不足获取数据的问题,提升了应用的可扩展性和灵活性。此外,GraphQL还支持实时数据订阅,可以用于构建实时聊天、消息推送等功能。 总而言之,学习GraphQL是现代Web开发中的重要一环,它将大大提升开发人员的效率和应用的功能性。通过掌握核心概念、查询语句和Schema语言,结合流行的库和框架,可以更好地应用GraphQL构建高性能、可扩展的API。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值