通过尝试引入GraphQL而获得的经验和感受。GraphQL或许可以成为救生板,就像泰坦尼克号上的救生板一样

GraphQL可以像泰坦尼克号的救生板一样从混乱中拯救出来,只要与实际实现相符。然而,并不是所有的项目都像泰坦尼克号一样需要救生板,所以如果不适合使用,这样的救生板也没有多大意义。

最近,我个人开发并发布的项目“node-node-node”的后端使用了Rails API以及GraphQL,它们与项目内容展现了最高的亲和性。

如果用一句话来概括GraphQL的优点,那就是“GraphQL可以承担客户端与服务器间所有复杂事务处理的责任”,详细解释这种复杂技术并没有其他优点。

1494257479002-RestfulServer.png
1494257483003-GraphQLServer.png

写一篇关于个人开发项目为什么会倾心于GraphQL的文章。在这个项目的实施中,关键是要解决REST中复杂的GET问题。
这个项目中涉及到了多次嵌套的层次结构,即在服务器的数据库中有多层节点,客户端需要从服务器中获取适当的节点信息。所有的节点都是按照以下的方式组织的,父子关系一直延续下去。

ジジイ、ババア、ジジイ、ババア
|
親、親、親、親
|
本人、兄弟、兄弟、兄弟
|
子、子、子、子
|
孫、孫、孫

为了尽可能使用户体验流畅,客户端会通过异步方式从服务器收集用户可能要点击的节点信息,并且这些节点信息是基于用户当前访问的节点。当用户准备点击时,只需展示事先在后台收集好的信息即可。
根据这种设计,根据用户所处的情境,需要在后台收集的节点会有所不同。

假设从上面的老人那边到用户自己那里下来的情况下,应该收集的节点信息只有子节点和孙子节点。原因是在从老人那边下来的过程中,已经拥有了比自己更高层的父节点的信息。

相反,如果是从孙子那边往上爬的用户的情况,应该收集的是父节点和老人的信息。在往上爬的过程中,已经拥有了子节点以下的信息。

如果是从兄弟的哥哥那边来的情况下,需要的是末子节点的信息。

所以,如果要以RESTful的方式仔细编写这个,会变成非常多的端点。

我只粗略地展示了来自父母、孩子和兄弟三种情况,但还有其他喜欢的信息,无论有与无,以及每个终点的独特信息的有无都是复杂的参数。由于我支持DHH的路由,所以只使用CRUD操作索引、显示、新建、编辑、创建、更新、删除,如果需要其他操作,就创建一个新的控制器。这样就会有大量的终点,由用React制作的客户端根据情况需要使用不同的终点,到底哪个是哪个变得混乱不堪,变得一团糟。

所以,通过将其迁移到GraphQL,查询语言将完全吸收客户端和服务器之间的复杂部分。客户端只需编写所需信息并发起请求,服务器将直接返回该信息。
此外,只有一个唯一的终点,没有版本或其他任何内容!

例如,当来自家长方面的用户想要子信息时,他们可以发送这样的请求:要求提供孩子的信息。

# client => server へのリクエスト(query)

query {
  Nodes {
    id
    message
    children {
      id
      message
    }
  }
}

那么服务器的响应将会是这样。

# client <= server レスポンス
 "data": {
    “Nodes”: {
      “id”: 3,
      “message”: “Hi I’m CEO.”,
      "children": [
        {
          “id”: 4,
          “message”: “Hi, I’m CTO.”
        }
      ]
    }
  }

简而言之,返回的响应与请求完全相同的形式。如果需要父级信息,只需在请求中添加“parent”即可。非常简单。

实现页面分页的方式如下。例如,如果有很多子节点且需要获取第三页,则可以使用children(page: 3)。

query {
  Nodes {
    id
    children(page: 3) {
      id
      message
    }
  }
}

大多数的网站并不仅仅需要一种客户所需信息。它可能包括文章信息、用户信息和通知用户的信息等。对于RESTful的情况,这些信息需要分开处理。

GET api/v1/users
GET api/v1/posts
GET api/v1/notifications

通过三次请求收集各自的信息,这样就能大致了解了。

在GraphQL中,您可以将所有内容一次性发送到一个请求中。

比如,把帖子(Posts)、用户(Users)和通知(Notifications)一起给我。

query {
  Posts(page: 0) {
    id
    message
  }
  User {
    id
    name
  }
  Notifications {
    id
    status
  }
}

当我们按照这种格式,得到一整块包含文章、用户和通知信息的响应。对于客户端来说,他们可以在需要时只获取指定的信息。(当我第一次实现并且真的按照这个方式收到响应时,不知怎的我笑出声了。)

最理想的的是极度轻量的GraphQL。

我在一篇文章中读到,如果在Rails中加入GraphQL,所有原本属于MVC的部分都会被集中到GraphQL中。这明显是错误的。当第一次遇到GraphQL时,可以理解为什么会感到兴奋并想将所有东西都放入GraphQL中,但官方网站并没有建议这么做。与过于臃肿的控制器一样,庞大的GraphQL不易于维护且难以操作。

正如GraphQL的作者Lee Byron所说:“GraphQL可以做任何事情,但最好保持它尽可能轻量。”GraphQL的责任和角色仅限于查询(类似RESTful的GET请求)和变更(类似POST或PATCH请求),除此之外没有其他职责。因此,如果resolve ->(obj, args, ctx)中存在逻辑,就需要考虑应该由谁负责这个职责,并将其移至相应的位置。

GraphQL慢吗?那是因为设计问题。

在有关GraphQL的负面文章中,对速度的提及很多。例如,“正常地缓存不起作用”之类的说法。这是错误的。实际上,在客户端和服务器端已经提出了多种缓存方法。实际上,在前面提到的节点-节点-节点的例子中,也使用了缓存。在大型查询中,如果没有缓存,响应时间需要耗费300毫秒,而通过缓存可以在3毫秒内返回。当然会使用缓存是理所当然的,但如果要讨论速度的话,不应该是缓存相关的问题,而应该是客户端能以多少毫秒获取到所需信息。即使某个GraphQL查询稍微慢一些,由于请求一次就可以收集完毕,与RESTful方式重复5、6次请求并等待“好了”的时间相比,并不一定慢。

N+1问题

有篇论述GraphQL导致N+1问题的贬低文章。已经很明确了,一旦出现N+1问题,将其归咎于查询语言是完全错误的。虽然确实可能在不经意间包含了N+1,但就算让步,那也不是GraphQL的问题,而是你的设计问题。

只要能够正确使用GraphQL,就能够感到幸福。

在本文的开头,GraphQL被比喻为泰坦尼克号上的救生艇,但许多人并没有意识到他们正在享受着船上的优雅用餐,而实际上他们所在的项目已经被钻了一个漏洞,即将沉没。由于GraphQL是一种热门技术,我认为最好确认一下它是否适用于当前的项目。

我对使用GraphQL的心得如下。

这是一个用GraphQL实现的项目,名为“在节点图中图解工程师的集体知识的SNS(桌面版)”。

用节点图解构工程师集体智慧的社交网络应用。
「节点-节点-节点(node-node-node)桌面版」
https://node-node-node.netlify.com/

bannerAds