• graphql 介绍


    graphql 是一种用于 API 的查询语言,对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,减少数据的冗余。

    example

    • 声明类型
    • type Project {
        name: String
        tagline: String
        contributors: [User]
      }
    • 查询语句
    • {
        project(name: "GraphQL") {
          tagline
        }
      }
    • 获取结果
    • {
        "project": {
          "tagline": "A query language for APIs"
        }
      }

      简单理解

      • 数据结构是以一种图的形式组织的


         
        图结构的数据
      • 与 RESTful 不同,每一个的 GraphQL 服务其实对外只提供了一个用于调用内部接口的endpoint,所有的请求都访问这个暴露出来的唯一端点。

      • GraphQL 实际上将多个 HTTP 请求聚合成了一个请求,它只是将多个 RESTful 请求的资源变成了一个从根资源 Post 访问其他资源的 schoolteacher等资源的图,多个请求变成了一个请求的不同字段,从原有的分散式请求变成了集中式的请求。

      特性

      请求你所要的数据
      • 可交互的查询 客户端请求字段,服务器根据字段返回,哪怕是数组类的结构依然可以根据字段名自由定制
      请求
      {
        hero() {
          name
          # friends 表示数组
          friends {
            name
          }
        }
      }
      
      返回
      {
        "data": {
          "hero": {
            "name": "R2-D2",
            "friends": [
              {
                "name": "Luke Skywalker"
              },
              {
                "name": "Han Solo"
              },
              {
                "name": "Leia Organa"
              }
            ]
          }
        }
      }
      
      • 使用参数查询
      // 请求
      {
        human(id: "1000") {
          name
        }
      }
      // 返回
      {
        "data": {
          "human": {
            "name": "Luke Skywalker",
            "height": 5.6430448
          }
        }
      }
      
      • 使用别名
        有的时候希望在一次请求过程中,对同一个字段使用不同的参数做两次请求
      // 请求hero字段两次,使用不同的参数
      {
          empireHero: hero(episode: EMPIRE) {
            name
          }
          jediHero: hero(episode: JEDI) {
            name
          }
      }
      
      // 返回
      {
        "data": {
          "empireHero": {
            "name": "Luke Skywalker"
          },
          "jediHero": {
            "name": "R2-D2"
          }
        }
      }
      
      • 片段(Fragments)
        片段使你能够组织一组字段,然后在需要它们的的地方引入,达到复用单元的意义。
      //请求
      {
        leftComparison: hero(episode: EMPIRE) {
          ...comparisonFields
        }
        rightComparison: hero(episode: JEDI) {
          ...comparisonFields
        }
      }
      
      fragment comparisonFields on Character {
        name
        appearsIn
        friends {
          name
        }
      }
      
      // 返回
      {
        "data": {
          "leftComparison": {
            "name": "Luke Skywalker",
            "appearsIn": [
              "NEWHOPE",
              "EMPIRE",
              "JEDI"
            ],
            "friends": [
              {
                "name": "Han Solo"
              },
              {
                "name": "Leia Organa"
              },
              {
                "name": "C-3PO"
              },
              {
                "name": "R2-D2"
              }
            ]
          },
          "rightComparison": {
            "name": "R2-D2",
            "appearsIn": [
              "NEWHOPE",
              "EMPIRE",
              "JEDI"
            ],
            "friends": [
              {
                "name": "Luke Skywalker"
              },
              {
                "name": "Han Solo"
              },
              {
                "name": "Leia Organa"
              }
            ]
          }
        }
      }
      
      • 变量
        客户端不需要每次拼接一个类似的query,通过提交不同的变量来实现
      // 查询语句
      query Hero($episode: Episode) {
        hero(episode: $episode) {
          name
        }
      }
      // 变量
      {
        "episode": "JEDI"
      }
      
      // 返回数据
      {
        "data": {
          "hero": {
            "name": "R2-D2"
          }
        }
      }
      
      • 内联数据块
        如果查询的字段返回的是接口或者联合类型,那么你可能需要使用内联片段来取出下层具体类型的数据:
      // 查询语句
      query HeroForEpisode($ep: Episode!) {
        hero(episode: $ep) {
          name
          ... on Droid {
            primaryFunction
          }
          ... on Human {
            height
          }
        }
      }
      // 变量
      {
        "ep": "JEDI"
      }
      // 返回数据
      {
        "data": {
          "hero": {
            "name": "R2-D2",
            "primaryFunction": "Astromech"
          }
        }
      }
      
      • 变更(Mutations)
        不只是查询,还能够变更数据
      mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
        createReview(episode: $ep, review: $review) {
          stars
          commentary
        }
      }
      
      // 变量
      {
        "ep": "JEDI",
        "review": {
          "stars": 5,
          "commentary": "This is a great movie!"
        }
      }
      
      //返回结果
      {
        "data": {
          "createReview": {
            "stars": 5,
            "commentary": "This is a great movie!"
          }
        }
      }
      
      // 完整的query 写法
      // query 是操作类型 query mutation subscription
      // HeroNameAndFriends 是操作名称
      query HeroNameAndFriends {
        hero {
          name
          friends {
            name
          }
        }
      }
      
      类型系统 (schema)

      example:

      // schema 文件入口
      schema {
        query: Query
        mutation: Mutation
      }
      // query 操作声明
      type Query {
        // 参数,声明该字段能够接受的参数
        hero(episode: Episode): Character
        droid(id: ID!): Droid
      }
      // 枚举类型
      enum Episode {
        NEWHOPE
        EMPIRE
        JEDI
      }
      
      //对象类型和字段
      type Character {
        //! 符号用于表示该字段非空
        name: String!
        appearsIn: [Episode]! // 字段类型是一个数组
      }
      
      // 接口类型
      interface Character {
        id: ID!
        name: String!
        friends: [Character]
        appearsIn: [Episode]!
      }
      
      // 实现特殊的接口
      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
      }
      
      input ReviewInput {
        stars: Int!
        commentary: String
      }
      
      • schema 文件入口
      schema {
        query: Query
        mutation: Mutation
      }
      
      • query 操作声明
      type Query {
        // 参数,声明该字段能够接受的参数
        hero(episode: Episode): Character
        droid(id: ID!): Droid
      }
      
      • 枚举类型
      enum Episode {
        NEWHOPE
        EMPIRE
        JEDI
      }
      
      
      • 对象类型和字段
      type Character {
        //! 符号用于表示该字段非空
        name: String!
        appearsIn: [Episode]! // 字段类型是一个数组
      }
      
      • 参数
      type Starship {
        id: ID!
        name: String!
        length(unit: LengthUnit = METER): Float // 可以使用默认值
      }
      
      • 接口类型
      interface Character {
        id: ID!
        name: String!
        friends: [Character]
        appearsIn: [Episode]!
      }
      
      • 输入类型
      input ReviewInput {
        stars: Int!
        commentary: String
      }
      
      • 实现特殊的接口的对象类型
      type Human implements Character {
        id: ID!
        name: String!
        friends: [Character]
        appearsIn: [Episode]!
        starships: [Starship]
        totalCredits: Int
      }
      
      • 基于接口类型的查找类型
        使用interface 类型 进行查找
      query HeroForEpisode($ep: Episode!) {
        hero(episode: $ep) {
          name
          ... on Droid {
            primaryFunction
          }
          ... on Human {
          }
        }
      }
      

      适用场景

      从更大的角度来看,GraphQL API 的主要应用场景是 API 网关,在客户端和服务之间提供了一个抽象层。

       
      image
      • 拥有包括移动端在内的多个客户端;

      • 采用了微服务架构,同时希望有效管理各个服务的请求接口(中心化管理);

      • 遗留 REST API 数量暴增,变得十分复杂;

      • 希望消除多个客户端团队对 API 团队的依赖;

      如果说grpc 面向过程的抽象,rest 面向的是资源的抽象,那么graphql 则是面向数据的抽象。所以graphql 更适合的场景是交互方更贴近数据的场景。

      数据中台与graphql

      中台数据的一些挑战和grapqhl能够提供的优势:

      • 丰富而异构的数据点以及挑战,对数据点的开发添加有效率上的要求
        graphql 在接口设计上据有很好的可扩展性,新加的数据点不需要新添加接口endpoint,只需要添加适合的字段名。对现有的接口影响也很小。

      • 多维度的数据模型的聚合,高度的复杂度,和服务更高耦合的接口,复杂度提升造成接口管理的困难。
        多维度的数据更容易使用图的结构描述,并且可以屏蔽各个服务调用细节,使用中心化的schema 管理数据,可以更靠近字段而非以接口为管理的单元。

      • 对应不同需求的用户调用
        B端/C端 用户调用需求个有不同,graphql 统一了调用方式,不需要为不同的目的定义不同的接口调用。如果各B 端用户对接口调用的方式有需求,只需要在graphql 服务之前做一次接口转换就可以,对现有系统侵入很少。

      应用方案

      通过 HTTP 提供服务
      • POST 请求
        {
        "query": "{me{name}}",
        "operationName": "...",
        "variables": { "myVariable": ""}
        }

      • 响应
        无论使用任何方法发送查询和变量,响应都应当以 JSON 格式在请求正文中返回。如规范中所述,查询结果可能会是一些数据和一些错误,并且应当用以下形式的 JSON 对象返回:
        {
        "data": { ... },
        "errors": [ ... ]
        }

      graphql 实现

      golang github.com/graphql-go/graphql

      func main() {
          // Schema
          fields := graphql.Fields{
              "hello": &graphql.Field{
                  Type: graphql.String,
                  Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                      return "world", nil
                  },
              },
          }
          rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields}
          schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)}
          schema, err := graphql.NewSchema(schemaConfig)
          if err != nil {
              log.Fatalf("failed to create new schema, error: %v", err)
          }
      
          // Query
          query := `
              {
                  hello
              }
          `
          params := graphql.Params{Schema: schema, RequestString: query}
          r := graphql.Do(params)
          if len(r.Errors) > 0 {
              log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors)
          }
          rJSON, _ := json.Marshal(r)
          fmt.Printf("%s 
      ", rJSON) // {“data”:{“hello”:”world”}}
      }
      

      N+1 问题

      graphql 作为的网关特点,在一次请求中可能会访问多个服务,在没有优化的情况下,往往会发送多个请求给后台服务。造成性能浪费

      {
         school {
            students { // n student
               .....
            }
         }
      }
      

      解决方案 DataLoader
      DataLoader被广泛地应用于解决[N+1查询问题]

      对于多个相同类别的数据使用同一个请求,传入多个id 返回多个数据。


       
      image.png
      var DataLoader = require('dataloader')
      var userLoader = new DataLoader(keys => myBatchGetUsers(keys));
      
      userLoader.load(1)
        .then(user => userLoader.load(user.invitedByID))
        .then(invitedBy => console.log(`User 1 was invited by ${invitedBy}`));
      
      // Elsewhere in your application
      userLoader.load(2)
        .then(user => userLoader.load(user.lastInvitedID))
        .then(lastInvited => console.log(`User 2 last invited ${lastInvited}`));
      
      

      缓存
      内存级别的缓存,load一次,DataLoader就会把数据缓存在内存,下一次再load时,就不会再去访问后台。

      var userLoader = new DataLoader(...)
      var promise1A = userLoader.load(1)
      var promise1B = userLoader.load(1)
      assert(promise1A === promise1B)
      

      可以自定义缓存策略等

      gprc 与 graphql (java)

      Rejoiner Generates a unified GraphQL schema from gRPC microservices and other Protobuf sources

      架构方案 schema 中心化/多版本

      • 多版本调用

      Schema 的管理去中心化,由各个微服务对外直接提供 GraphQL 请求接口,graphql service通过请求的字段名陆游到各个服务 同时将多个服务的 Schema 进行合并


       
      粘合schema

      优点:

      • schema 粘合,以此来解决开发的效率问题。对于新的数据模块(粗粒度的服务),只需要提供最新的模块的schema,解决相同类型数据的冲突,graphql service 就能够自动提供merged 之后的schema。

      缺点:

      • 每个微服务需要提供graph 接口,对接schema,使得微服务耦合了graphql 接口。
      • 同名的类型需要解决冲突,但是解决冲突的方案可能包含业务逻辑,灵活性不是最高
      • 粘合的功能可能还需要承载服务发现以及流量路由等功能,复杂度高,稳定性要求高
      • 目前比较成熟的Schema Stitching方案只有基于nodejs 的,社区还不完善。

      但是只找到了 javascript 解决方案

      import {
        makeExecutableSchema,
        addMockFunctionsToSchema,
        mergeSchemas,
      } from 'graphql-tools';
      
      // Mocked chirp schema
      // We don't worry about the schema implementation right now since we're just
      // demonstrating schema stitching.
      const chirpSchema = makeExecutableSchema({
        typeDefs: `
          type Chirp {
            id: ID!
            text: String
            authorId: ID!
          }
      
          type Query {
            chirpById(id: ID!): Chirp
            chirpsByAuthorId(authorId: ID!): [Chirp]
          }
        `
      });
      
      addMockFunctionsToSchema({ schema: chirpSchema });
      
      // Mocked author schema
      const authorSchema = makeExecutableSchema({
        typeDefs: `
          type User {
            id: ID!
            email: String
          }
      
          type Query {
            userById(id: ID!): User
          }
        `
      });
      
      addMockFunctionsToSchema({ schema: authorSchema });
      
      export const schema = mergeSchemas({
        schemas: [
          chirpSchema,
          authorSchema,
        ],
      });
      
      • 中心化调用
        一个中心化的schema和graphql service,各个微服务提供rpc 接口或者rest api接口,graphql service主动调用别的微服务rpc 接口,按照schema进行组合最后返回给前端。
       
      graphql service主动组合各个服务

      优点:

      • 对于子系统没有侵入,各个微服务和graphql 没有耦合。
      • graphql作为网关服务有更强的控制粒度,更加灵活,更加容易附加业务逻辑(验证,授权等)。

      缺点:

      • 接口聚集之后,如果接口频繁改动,对与graphql service 开发压力更大,流程上都依赖于graph 网关服务。
      • 对于后端数据服务的职责划分要求更高。不宜把过重的业务逻辑放置到graphql service 中

      架构想象

      缺失的版图:
      由于graphql是面向数据的接口,所以架构上面必然需要有能力去描述这种图的数据模型。这样更接近本质。个人觉得目前生态中缺少一个面向数据图的服务级别的粘合器,可以中心化配置,灵活调用各种局部解析器,将整个微服务集群,从数据的角度组织成一张网络(graph)。


       
      graph technical.png

      使用复合模式,综合多schema / 单schema 的优点:
      可以通过代码或者扩展组建定制化,同时使用一些类schema (grpc protocl)代码自动生成graph schema,结合二者的数据结构。
      可以中心化配置,整体对于graph 有统一的对外结构。

      微服务集群需要与graphql解耦:
      graphql service 不应该和微服务有过高的耦合,一些服务中间建的功能应该从graphql service移除,例如服务发现和负载均衡,流量控制等。

       
       
      3人点赞
       
      coding
       
       


      作者:xuyaozuo
      链接:https://www.jianshu.com/p/da1260b95faf
      来源:简书
      著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    声明 欢迎转载,但请保留文章原始出处:) 博客园:https://www.cnblogs.com/chenxiaomeng/ 如出现转载未声明 将追究法律责任~谢谢合作
  • 相关阅读:
    坚持的力量 第十五篇
    <QT障碍之路>qt中使用串口类接收数据不完整
    《开发板 — ping得通本地,但是不能上网。route设置默认网关》
    《海思3531D 搭建环境出现的一些问题》
    《应用编程 — 进程通信 — 命名管道FIFO》
    <QT学习>QT中串口QSerialport类学习与使用
    网络视频工具/网站
    [恢]hdu 1701
    [恢]hdu 1425
    [恢]hdu 2523
  • 原文地址:https://www.cnblogs.com/chenxiaomeng/p/15382719.html
Copyright © 2020-2023  润新知