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

相關文章
相關標籤/搜索