5个用/不用GraphQL的理由

我在如何使用Gatsby创建博客 / How to build a blog with Gatsby这篇文章中提过GraphQL在Gatsby中的应用。总的来说,它是一个新潮的技术,在适宜的使用场景威力无穷。这里咱们来讨论一下用/不用GraphQL的理由吧。javascript

简单介绍GraphQL 

GrahQL

GraphQL是Facebook2015年开源的数据查询规范。现今的绝大多数Web Service都是RESTful的,也就是说,client和server的主要沟通模式仍是靠client根据本身的须要向server的若干个endpoint (url)发起请求。因为功能的日渐丰富,对Web Application的要求变得复杂,REST的一些问题逐渐暴露,人们开始思考如何应对这些问题。GraphQL即是具备表明性的一种。GraphQL这个名字,Graph + Query Language,就代表了它的设计初衷是想要用相似图的方式表示数据:即不像在REST中,数据被各个API endpoint所分割,而是有关联和层次结构的被组织在一块儿。html

比方说,假设这么一个提供user信息的REST API: <server>/users/<id>,和提供用户的关注者的API:<server>/users/<id>/followers,以及该用户关注对象的API: <server>/users/<id>/followed-users。传统的REST会须要3次API call才能请求出这三份信息(假设<server>/users/<id> 没有包含followers and followed-users信息,which will be a definite redundancy if it does):
1 GET <server>/users/<id>前端

{
 "user": {
    "id" : "u3k2k3k178",
    "name" : "graph_ql_activist",
    "email" : "graph_ql@activist.com",
    "avatar" : "img-url"
  }
}

2 GET <server>/users/<id>/followed-users
3 GET <server>/users/<id>/followersjava

然而若是使用GraphQL,一次API请求便可获取全部信息而且只选取须要的信息(好比关于用户只须要name不要email, followers只要最前面的5个name,followed-users只要头像等等):react

query {
  user (id : "u3k2k3k178") {
    name followers (first: 5) {
      name
    }
    followed-users {
      avatar
    }
  }
}

咱们会获得一个彻底按照query定制的,很少很多的返回结果(通常是一个json对象)。git

5个使用GraphQL的理由

使用GraphQL的理由, 必然是从讨论RESTful Service的局限性和问题开始。github

  1. 数据冗余和请求冗余 (overfetching & underfetching)
  2. 灵活而强类型的schema
  3. 接口校验 (validation)
  4. 接口变更,维护与文档
  5. 开发效率

1 数据冗余和请求冗余 (overfetching & underfetching)

根据users API的例子,咱们能够想见,GET用户信息的REST call,咱们就算只是想要一个用户的一两条信息(好比name & avatar),经过该API,咱们也会获得他的整个信息。所谓的overfetching就是指的这种状况——请求包含当前不须要的信息。这种浪费会必定程度地总体影响performance,毕竟更多的信息会占用带宽和占用资源来处理。数据库

一样从上面的例子咱们能够看出来,在许多状况下,若是咱们使用RESTful Application,咱们经常会须要为联系紧密并总量不大的信息,对server进行屡次请求,call复数个API。json

举一个例子,获取ID为"abc1"和"abc2"的两个用户的信息,咱们可能都须要两个API call,一百个用户就是一百个GET call,这是否是很莫名其妙呢?这种状况其实就是underfetching——API的response没有合理的包含足够信息。后端

然而在GraphQL,咱们只须要很是简单地改变schema的处理方式,就能够用一个GET call解决:

query {
  user (ids : ["ab1", "abc2", ...])
}

咱们新打开一个网页,若是是RESTful Application,可能请求数据就会立刻有成百上千的HTTP Request,然而GraphQL的Application则可能只须要一两个,这至关于把复杂性和heavy lifting交给了server端和cache层,而不是资源有限,而且speed-sensitive的client端。

2 灵活而强类型的schema

GraphQL是强类型的。也就是说,咱们在定义schema时,相似于使用SQL,是显式地为每个域定义类型的,好比说:

type User {
  id: ID!
  name: String!
  joinedAt: DateTime!
  profileViews: Int! @default(value: 0)
}

type Query {
  user(id: ID!): User
}

GraphQL的schema的写做语言,其实还有一个专门的名称——Schema Definition Language (SDL)。

这件事情的一大好处是,在编译或者说build这个Application时,咱们就能够检查并应对不少mis-typed的问题,而不须要等到runtime。同时,这样的写做方式,也为开发者提供了巨大的便利。好比说使用YAML来定义API时,编写自己就是十分麻烦的——可能没有理想的auto-complete,语法或者语义有错没法及时发现,文档也须要本身当心翼翼地编写。就算有许多工具(好比Swagger)帮助,这仍然是一个很使人头疼的问题。

3 接口校验 (validation)

显而易见,因为强类型的使用,咱们对收到的数据进行检验的操做变得更为容易和严格,自动化的简便度和有效性也大大提升。对query自己的结构的校验也至关因而在schema完成后就自动获得了,因此咱们甚至不须要再引入任何别的工具或者依赖,就能够很方便地解决全部的validation。

4 接口变更,维护与文档

RESTful Application里面,一旦要改动API,无论是增删值域,改变值域范围,仍是增减API数量,改变API url,都很容易变成伤筋动骨的行为。

若是说改动API url(好比/posts --> /articles),咱们思考一下那些地方可能要改动呢?首先client端的代码定然要改变request的API endpoint;中间的caching service可能也须要改要访问的endpoint;若是有load balancer, reverse proxy,那也可能须要变更;server端本身固然也是须要作相应改变的,这根据application本身的编写状况而定。

相比之下,GraphQL就轻松多了。GraphQL的Service,API endpoint极可能就只有一个,根本不太会有改动URL path的状况。至始至终,数据的请求方都只须要说明本身须要什么内容,而不须要关心后端的任何表述和实现。数据提供方,好比server,只要提供的数据是请求方的母集,不论它们各自怎么变,都不须要由于对方牵一发而动全身。

在现有工具下,REST API的文档没有到过度难以编写和维护的程度,不过跟能够彻底auto-generate而且可读性能够很好地保障的GraphQL比起来,仍是略显逊色——毕竟GraphQL甚至不须要咱们费力地引入多少其余的工具。

再一点,咱们都知道REST API有一个versioning: V1, V2, etc.这件事很是的鸡肋并且很是麻烦,有时候还要考虑backward compatibility。GraphQL从本质上不存在这一点,大大减小了冗余。增长数据的fields和types甚至不须要数据请求方作任何改动,只须要按需添加相应queries便可。

另外,有了GraphQL的queries,咱们能够很是精准地进行数据分析(Analytics)。好比说具体哪些queries下的fields / objects在哪些状况下是被请求的最多/最频繁的——而不像RESTful Application中,若是不进行复杂的Analytics,咱们只能知道每一个API被请求的状况,而不是具体到它们内含的数据。

5 开发效率

相信上面说的这些点已经充分可以说明GraphQL对于开发效率可以获得怎样的提高了。

再补充几点。

GraphQL有一个很是好的ecosystem。因为它方便开发者上手和使用-->你们争相为它提供各类工具和支持-->GraphQL变得更好用-->社区文化和支持更盛-->... 如同其余好的开源项目同样,GraphQL有着一个很是好的循环正向反馈。

对于一套REST API,哪怕只是其使用者(consumer),新接触的开发者须要必定时间去熟悉它的大体逻辑,要求乃至实现。然而GraphQL使用者甚至不须要去看相似API文档的东西,由于咱们能够直接经过query查询query里面全部层级的type的全部域和它们各自的type,这不得不说很方便:

{
  __schema {
    types {
      name
    }
  }
}

==> 咱们能够看到query所涉及的全部内容的类型:

{
  "data": {
    "__schema": {
      "types": [
        {
          "name": "Query"
        },
        {
          "name": "Episode"
        },
        {
          "name": "Character"
        },
        {
          "name": "ID"
        },
        {
          "name": "String"
        },
        {
          "name": "Int"
        },
        {
          "name": "FriendsConnection"
        },
        {
          "name": "FriendsEdge"
        },
        {
          "name": "PageInfo"
        }
        {
          "name": "__Schema"
        },
        {
          "name": "__Type"
        },
        {
          "name": "__TypeKind"
        },
        {
          "name": "__Field"
        },
        {
          "name": "__InputValue"
        },
        {
          "name": "__EnumValue"
        }
        }
      ]
    }
  }
}

对于GraphQL,我还有个很是我的的理由偏心它:对于API的测试,相比于比较传统的Postman或者本身写脚本进行最基本的http call(或者curl),我更喜欢使用insomnia这个更为优雅的工具。而在此之上,它还很是好地支持了GraphQL,这让个人开发和测试体验变得更好了。(Postman至今还不支持GraphQL,虽然本质上咱们能够用它make GraphQL query call)

5个不用GraphQL的理由

  1. 迁移成本
  2. 牺牲Performance
  3. 缺少动态类型
  4. 简单问题复杂化
  5. 缓存能解决不少问题

1 使用与迁移成本

现有的RESTful Application若是要改形成GraphQL Application?

hmmm...

咱们须要三思。首先我就不说RESTful原本从end to end都有成熟高效解决方案这样的废话了。迁移的主要问题在于,它从根本上改变了咱们组织并暴露数据的方式,也就是说对于application自己,从数据层到业务逻辑层,可能有极其巨大的影响。因此它很是不适合现有的复杂系统“先破后立”。一个跑着SpringMVC的庞大Web Application若是要改为时髦的GraphQL应用?这个成本和破坏性难以预计。

而且,尽管咱们说GraphQL有着很好的社区支持,但本质上使用GraphQL,就等于要使用React与NodeJS。因此若是并非正在使用或者计划使用React和Node,GraphQL是不适合的。

2 牺牲Performance

Performance这件事是无数人所抱怨的。如同咱们前面所说的,GraphQL的解决方案,至关于把复杂性和heavy lifting从用户的眼前,移到了后端——不少时候,就是数据库。

要讨论这一点,咱们首先要提的是,为了支持GraphQL queries对于数据的查询,开发者须要编写resolvers。

好比说这样一个schema:

type Query {
  human(id: ID!): Human
}

type Human {
  name: String
  appearsIn: [Episode]
  starships: [Starship]
}

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

type Starship {
  name: String
}

对于human,咱们就须要一个最基础的resolver:

Query: {
  human(obj, args, context, info) {
    return context.db.loadHumanByID(args.id).then(
      userData => new Human(userData)
    )
  }
}

固然这还没完,对不一样的请求类型,咱们要写不一样的resolver——不只原来REST API的CRUD咱们都要照顾到,可能还要根据业务需求写更多的resolver。

这件事情形成的影响,除了开发者要写大量boilerplate code之外,还可能致使查询性能低下。一个RESTful Application,因为每一个API的肯定性,咱们能够针对每个API的逻辑,很是好的优化它们的性能,因此就算存在必定程度的overfetching/underfetching,先后端的性能均可以保持在可以接受的范围内。然而想要更普适性一些的GraphQL,则可能会由于一个层级结构复杂并且许多域都有很大数据量的query跑许多个resolvers,使得数据库的查询性能成为了瓶颈。

3 缺少动态类型

强类型的schema当然很省力,可是若是咱们有时候想要一些自由(flexibility)呢?

比方说,有时候请求数据时,请求方并不打算定义好须要的全部层级结构和类型与域。比方说,咱们想要单纯地打印一些数据,或者获取一个user的一部分fields直接使用,剩下部分保存起来以后可能使用可能不使用,但并不肯定也不关心剩下的部分具体有那些fields——多余的部分可能做为additional info,有些域若是有则使用,没有则跳过。

这只是一个例子,可是并非一个钻牛角尖的例子——由于有时候咱们所要的objects的properties原本就多是dynamic的,咱们甚至可能会经过它的properties/fields来断定它是一个怎样的object。

咱们要怎么处理这种问题呢?一种有些荒诞现实主义的作法是,往Type里加一个JSON string field,用来提供其相关的全部信息,这样就能够应对这种状况了。可是这是否是一个合理的作法呢?

4 简单问题复杂化

最显著的例子,就是error handling。REST API的状况下,咱们不须要解析Response的内容,只须要看HTTP status code和message,就能知道请求是否成功,大概问题是什么,处理错误的程序也十分容易编写。

然而GraphQL的情景下,hmmm...

只要Service自己还在正常运行,咱们就会获得200的HTTP status,而后须要专门检查response的内容才知道是否有error:

{
      "errors": [
        {
          "message": "Field \"name\" must not have a selection since type \"String\" has no subfields.",
          "locations": [
            {
              "line": 31,
              "column": 101
            }
          ]
        }
      ]
    }

Another layer of complexity.

同时,简单的Application,使用GraphQL实际上是很是麻烦的——好比前面提到的resolvers,须要大量的boilerplate code。另外,还有各类各样的Types, Queries, Mutators, High-order components须要写。相比之下,反却是REST API更好编写和维护。

5 缓存能解决不少问题

编写过HTTP相关程序以后应该都知道,HTTP自己就是涵盖caching的,更不要提人们为了提升RESTful Application的performance而针对缓存做出的种种努力。

对于overfetching和请求次数冗余的问题,假设咱们的整个application作了足够合理的设计,而且因为REST API的固定和单纯性,缓存已经能很是好地减小大量的traffic。

然而若是选择使用GraphQL,咱们就没有了那么直白的caching解决方案。首先,只有一个API endpoint的状况下,每一个query均可能不一样,咱们不可能很是轻松地对request分门别类作caching。固然并非说真的没有现成的工具,好比说Appollo client就提供了InMemoryCache而且,不论有多少queries,老是有hot queries和cold ones,那么pattern老是有的。针对一些特定的query咱们还能够定向地缓存,好比说PersistGraphQL即是这样一个工具。然而这样作其实又是至关于从queries中提炼出相似于原来的REST API的部分了,而且又增长了一层complexity,无论是对于开发仍是对于performance,这均可能有不容忽视的影响。

总结

GraphQL最大的优点,就是它可以大大提升开发者的效率,并且最大化地简化了前端的数据层的复杂性,而且使得先后端对数据的组织观点一致。只是使用时,须要考察scale, performance, tech stack, migration等等方面的要求,作合理的trade-off,不然它可能不只没能提升开发者效率,反倒制造出更多的问题。

References

via:https://www.jianshu.com/p/12dff5905cf6

相关文章
相关标签/搜索