Try   HackMD

2019-GraphQL-簡介

tags: Tech Note 2019 Introduction GraphQL

REST 與 GraphQL 比較

REST

  • 簡易介紹:

表現層狀態轉換
英語:Representational State Transfer
REST是設計風格而不是標準
在2000年出現。

資源是由URI來指定。
使用HTTP協定提供的GET、POST、PUT和DELETE方法。

  • 優點:
    • 系統撰寫靈活。
    • 風格統一,直觀簡潔的URL。
    • api獨立不互相影響。
    • 在瀏覽器輸入URL即可獲取資料。
    • 目前主流風格。
  • 缺點:
    • 參數、回傳值型別不固定。
    • 不容易處理巢狀資源。
    • 一個URL只能獲取特定資源,結構鬆散問題導致多條件查詢得創造多個URL。
    • 因為無狀態,每一次只能傳輸一筆資料,無法有效的整合所需資料一次性取得。
    • 會拿多餘的欄位,一次回傳的資料都為完整的結構,無法取捨不需要的資料,以至於傳輸量的增加。
    • 不斷成長的 Endpoint 數量。

GraphQL

  • 簡易介紹:

GraphQL 是一種為 API 設計的資料查詢(修改)的語言
由Facebook及社區開發。
在2012年出現在2015公開釋出。

大概念上有點類似 SQL。
GraphQL 服務是通過定義類型和類型上的字段來創建的,
然後給每個類型上的每個字段提供解析函數。

  • 出現原因:

    • 資料傳遞速度嚴重影響效能。
    • 不同平台所需的資料數量、格式都不同。
    • 前後端溝通難度增加。
    • Legacy API 難以處理。
    • 結構鬆散與查詢分割過多,導致URL過多。
  • 優點:

    • 精準資料取得。
    • 資料只拿剛好且彈性十足。
    • 前後端溝通成本減少。
    • 程式即文檔。
    • 前端控制權提升。
    • 高度自由的實作方式。
    • 不預設綁任何程式語言 (language agnostic) 或是資料庫 (DB agnostic)。
    • 可將不同 micro service 的 GraphQL schema 串接在一起。
    • 強型別,型別錯就直接被擋下來。
    • 支援五種基礎型別 (Scalar Types)。
    • 能自定義型別。
  • 缺點:

    • 沒有一定的實作規範,可能因為前後端對於架構的疏忽或不了解導致設計出過於複雜的 Schema。
    • 仍是一種新技術(?)相關社群仍在開發中。
    • 很容易一不小心陷入 RESTful API 的設計思維、埋下更多技術債。
    • Server Side Caching 實作困難。

簡易介紹

簡單來說:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

設計:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

有了 GraphQL
可以把商業模型 (business model) 像圖形 (graph) 一樣串連起來。

Query 是 Client side 是符合 schema 規定的查詢語言格式。

Schema 是 Server side 定義整體資料結構格式。


資料架構(Schema)

Schema 大概結構圖







hierarchy



schema

schema



QueryMain

Query



schema->QueryMain





MutationMain

Mutation



schema->MutationMain





A function

A function



QueryMain->A function





B function

B function



QueryMain->B function





C function

C function



MutationMain->C function





D function

D function



MutationMain->D function





type

type



A function->type





B function->type





input

input



B function->input





C function->type





C function->input





D function->input





Int , Float , String , Boolean , ID , scalar

Int , Float , String , Boolean , ID , scalar



type->Int , Float , String , Boolean , ID , scalar





input->Int , Float , String , Boolean , ID , scalar






快速簡介

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

大致分別

  • 型別 (type)

    • Scalar Type
    • Object Type
  • 遞迴 (recursive) 取值

    • 展開並選取需要的資料。
    • query 結構最末端的 field 一定要是 Scalar Type。

範例

宣告時的結構:

schema的整合
schema {
  query: Query
}
Query的功能表

type Query {
 LogIn(ID: String!, Password: String!): LogInToken!
}
回傳的Object
type LogInToken {
  Status: StatusData!
  GetTimes: String!
  AccountToken: String!
  AccountID: String!
}
在LogInToken Object 中的 Object

type StatusData {
  StatusCode: Int!
  Description: String!
}

實際請求:

query {
  LogIn(
  ID:"abcde@gmail.com",
  Password:"123456789"
  ){
    Status{
      StatusCode
      Description
    }
    GetTimes
    AccountID
    AccountToken
  }
}

實際回傳:

{
  "data": {
    "LogIn": {
      "Status": {
        "StatusCode": 2,
        "Description": "Success LogIn."
      },
      "GetTimes": "2019-01-21 04:15:40.181350574 +0000 UTC",
      "AccountID": "abcde@gmail.com",
      "AccountToken": "31353035303433373931353438303434313430616263646540676d61696c2e636f6d313431313830353236302e3339383939343230373438833fca8774f34edfa7095cfb7cb05ed549d34e22be073aaa644d7ef100d10c2deb7b37fc425c7384359abd21d5a549647ae9a161a51c860d7deffe20a0b19f"
    }
  }
}


主要Schema

介紹

每一個 GraphQL 服務都有一個 query 類型。
可能有一個 mutation 類型。
這兩個類型和常規對像類型無差,但是它們之所以特殊,是因為它們定義了每一個 GraphQL 查詢的『入口』。

可明確分別功能所使用的schema。

範例

schema {
  query: Query
  mutation: Mutation
}

查詢結構

介紹

一個 GraphQL schema 中的最基本的組件是對像類型,它就表示你可以從服務上獲取到什麼類型的對象,以及這個對像有什麼字段。
並且有以下功能:

  • 可建立多個對象類型。

  • 可附加參數。

  • 明確制訂回傳型態。

  • 可強制非空,也可否。

  • 可使用『類型』當作輸入或回傳(輸入的類型必須使用input的type,後面將會說明)。

  • 可使用數組回傳。

範例

type Query {
    # GraphQL 對象類型 
    users(
    # 可附加參數(!表示『必定』為非空,沒!則否)
    id:String
    ): [User!]! # 回傳結構([]表示數組)
    
    post: [Post!]!
    
}

備註

如果這個參數上傳遞了一個空值(不管通過 GraphQL 字符串還是變量),那麼會導致服務器返回一個驗證錯誤。

範例

myField: [String!]
有效:
myField: null 
myField: [] 
myField: ['a', 'b']


錯誤:
myField: ['a', null, 'b']
myField: [String]!
有效:
myField: []
myField: ['a', 'b']
myField: ['a', null, 'b']


錯誤:
myField: null

資料結構

介紹

GraphQL 自帶一組默認標量類型(Scalar type)

  • Int:有符號 32 位整數。
  • Float:有符號雙精度浮點值。
  • String:UTF‐8 字符序列。
  • Boolean:true 或者 false。
  • ID:ID 標量類型表示一個唯一標識符,通常用以重新獲取對像或者作為緩存中的鍵。ID 類型使用和 String 一樣的方式序列化;然而將其定義為 ID 意味著並不需要人類可讀型。

GraphQL 也可使用自定義組合類型(Object type)

這種『自定義且』、『能展開』的類型稱為Object type。

type A {
    B:String
    C:ID
    D:Int
}

備註

大部分的 GraphQL 服務實現中,都有『自定義標量類型』的方式。

例如,我們可以定義一個 Date 類型:

scalar Date 

然後就取決於我們的實現中。

例如,你可以指定 Date 類型應該總是被序列化成整型時間戳,而客戶端應該知道去要求任何 date 字段都是這個格式。

範例

type User {
    id: ID!
    name: String!
    number(unit: LengthUnit = METER): Float 
    # 回傳可附帶參數,每一個參數都必須是具名的。
    # 參數可能是可選或必選,當參數為可選時,可制訂一個默認。
}

type Post {
    id: ID!
    title: String!
    body: String!
}


類型

枚舉類型

枚舉類型是一種『特殊的標量』
它『限制』在一個特殊的『可選值集合』內。

驗證這個類型的任何參數是可選值的的某一個,
一個字段總是一個有限值集合的其中一個值。

下面是一個用 GraphQL schema 語言表示的 enum 定義:

enum Episode {
    NEWHOPE
    EMPIRE
    JEDI 
} 

這表示無論我們在 schema 的哪處使用了 Episode
都可以肯定它返回的是 NEWHOPE、EMPIRE 和 JEDI 之一。

接口類型

一個接口是一個抽像類型,它包含某些字段
而對像類型必須包含這些字段,才能算實現了這個接口。

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

這意味著任何實現 Character 的類型都要具有這些字段,並有對應參數和返回類型。

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

聯合類型

聯合類型和接口十分相似,但是它並不指定類型之間的任何共同字段。

任何返回一個 SearchResult 類型的地方,都可能得到一個 Human、Droid 或者 Starship。

注意,聯合類型的成員需要是具體對像類型;你不能使用接口或者其他聯合類型來創造一個聯合類型。

union SearchResult = Human | Droid | Starship

範例

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

輸入類型

我們只討論過將例如『枚舉』和『字符串等標量值』作為參數傳遞給字段

但是你也能很容易地傳遞複雜對象。

在 GraphQL schema language 中
輸入對像看上去和常規對像一模一樣,除了關鍵字是 input 而不是 type

input ReviewInput {
  stars: Int!
  commentary: String
}

開發模式


1. REST + GraphQL Hybrid

如果直接建立一個新的GraphQL,會造成前端維護的困難,因為要同時處理 REST 與 GraphQL 的結構。
透過虛擬轉換的方式間接導入 GraphQL。
利用擴充的方式,將RESTful的部分抽象出來,加以設計。

引用圖片


2. GraphQL Layer

把 GraphQL Server 抽出來放到中間當作 GraphQL Layer。
既可以處裡API引用也可以直接處理DB。

引用圖片


3. GraphQL API Gateway

GraphQL Gateway 不直接與 Database 溝通,
而是把前端的 Requests 需求往後面的 API Service 送。
而後面直接與 Database 串接的 GraphQL Server 稱為 GraphQL Native。

引用圖片


Demo

使用Framework:99designs/gqlgen
套件Github:https://github.com/99designs/gqlgen
文檔:https://gqlgen.com/
開發時間: 11 天 (2019/1/15 ~ 2019/1/17)
PS.包含新的DB引用、新結構的重構(完全不使用公司內的任何套件),寫資料等。
可使用數量: 2 個
可使用功能:Optend、Generalreport

目前架構

起動流程







hierarchy



Start

Start



Config

Config



Start->Config





Base

Base



Start->Base





Golang

Golang



Config->Golang


Load



Base->Golang


Load



Start GraphQL Gin

Start GraphQL Gin



Golang->Start GraphQL Gin





Start RESTful Gin

Start RESTful Gin



Golang->Start RESTful Gin






系統流程







hierarchy



Gin

Gin



GraphQL API

GraphQL API



Gin->GraphQL API





Gin->GraphQL API


Run



RESTful API

RESTful API



Gin->RESTful API





Gin->RESTful API


Run



GraphQL Play

GraphQL Play



Gin->GraphQL Play


Run



Other

Other



Gin->Other


Run



Controller

Controller



GraphQL API->Controller


   Examination(檢查)



RESTful API->Controller


   Examination(檢查)



Data Struct

Data Struct



GraphQL Play->Data Struct


  測試用



Request

Request



Request->Gin





Return Error

Return Error



Controller->Return Error


不符合



MySQL Database Main

MySQL Database Main



Controller->MySQL Database Main


符合









hierarchy



MySQL Database Main

MySQL Database Main



Database Template

Database Template



MySQL Database Main->Database Template


多工 or 等待所有資料取得



Extend

Extend



MySQL Database Main->Extend


例外處理



Return Error

Return Error



MySQL Database Main->Return Error


內部錯誤



Database Template->MySQL Database Main


處理完的資料



Calculation

Calculation



Database Template->Calculation


等待所有資料完成



Database Template->Return Error


內部錯誤



Extend->Database Template


例外處理



Extend->Return Error


內部錯誤



Map to Struct

Map to Struct



Calculation->Map to Struct


整理成結構回傳



Calculation->Return Error


內部錯誤



Map to Struct Extend

Map to Struct Extend



Map to Struct->Map to Struct Extend


特別處理



Return Success Data

Return Success Data



Map to Struct Extend->Return Success Data


     無誤後回傳



請求格式

URL

Headers

  • Accept-Encoding
    • gzip
  • Content-Type
    • application/json
  • Accept
    • application/json

Body

Optend

一般

結構
query {
  OpttrEnd(Information:{
    token:"12345678",
    starttime:"2018-10-01",
    endtime:"2018-11-01"
  }){
    name,
    code,
    data{
      day,
      amount,
    }
  }
}
傳送內容
{"query":"\nquery {\nOpttrEnd(Information:{\ntoken:\"12345678\",\nstarttime:\"2018-10-01\",\nendtime:\"2018-11-01\"\n}){\nname,\ncode,\ndata{\nday,\namount,\n}\n}\n}"}

測試結果

{
"data": {
    "OpttrEnd": [
        {
            "name": "入款总计",
            "code": "deposit",
            "data": [
                {
                    "day": "2018-10-01T00:00:00Z",
                    "amount": "0"
                },...
            ]
        },
        {
            "name": "出款总计",
            "code": "withdraw",
            "data": [
                {
                    "day": "2018-10-01T00:00:00Z",
                    "amount": "0"
                },....
            ]
        },
        {
            "name": "营利总计",
            "code": "profit",
            "data": [
                {
                    "day": "2018-10-01T00:00:00Z",
                    "amount": "-33.53"
                },....
            ]
        }
    ]
}}

錯誤

  • 時間漏打
結構
query {
  OpttrEnd(Information:{
    token:"12345678",
    starttime:"2018-1-01",
    endtime:"2018-11-01"
  }){
    name,
    code,
    data{
      day,
      amount,
    }
  }
}
傳送內容
{"query":"\nquery {\nOpttrEnd(Information:{\ntoken:\"12345678\",\nstarttime:\"2018-10-01\",\nendtime:\"2018-11-01\"\n}){\nname,\ncode,\ndata{\nday,\namount,\n}\n}\n}"}

測試結果

{
  "errors": [
    {
      "message": "Time Error",
      "path": [
        "OpttrEnd"
      ]
    }
  ],
  "data": {
    "OpttrEnd": null
  }
}

Generalreport

一般

結構


query Generalreport{
  Generalreport(
    Information:{
    token:"12345678",
    starttime:"2018-10-01",
    endtime:"2018-11-01"
    },
    Extend:{
      Level:"1"
    }){
    ChannelName
    ChannelCode
    WalletCode
    Others
    Items{
      name
      level
      ordercount
      total
      earning
      point
      prize
    }
    Pager{
      Index
  Pages
  Size
  Total
  TotalOrderCount
  Amount
  # SubAmount
  # Point
  # SubPoint
  # Earning
  # SubEarning
  # WinLose
  # Income
  # SubIncome
  # TotalPrize
  # Deposit
  # SubDeposit
  # Withdraw
  # SubWithdraw
  # Discount
  # SubDiscount
  # Rebate
  # SubRebate
  # Payout
  # SubPayout
  # Revenue
  # Times
  # SubTimes
  # Charge
  # SubCharge
  # TotalDiscountAmount
  # TotalDiscountCount
  # TotalPointThreshold
  # Frozen
  # SubFrozen
  # TotalDepositCount
  # TotalWithdrawCount
  # TotalDepositAmount
  # TotalWithdrawAmount
  # TotalTransferCharge
  # TotalDepositCharge
  # TotalWithdrawCharge
  # TotalDepositDiscount
  # RegisterCount
  # LoginCount
  # FirstDepositCount
  # FirstDepositAmount
  # FirstDepositCharge
    }
  }
}
  
傳送內容
{"query":"\n\nquery Generalreport{\n  Generalreport(\n    Information:{\n    token:\"12345678\",\n    starttime:\"2018-10-01\",\n    endtime:\"2018-11-01\"\n    },\n    Extend:{\n      Level:\"1\"\n    }){\n    ChannelName\n    ChannelCode\n    WalletCode\n    Others\n    Items{\n      name\n      level\n      ordercount\n      total\n      earning\n      point\n      prize\n    }\n    Pager{\n      Index\n  Pages\n  Size\n  Total\n  TotalOrderCount\n  Amount\n  # SubAmount\n  # Point\n  # SubPoint\n  # Earning\n  # SubEarning\n  # WinLose\n  # Income\n  # SubIncome\n  # TotalPrize\n  # Deposit\n  # SubDeposit\n  # Withdraw\n  # SubWithdraw\n  # Discount\n  # SubDiscount\n  # Rebate\n  # SubRebate\n  # Payout\n  # SubPayout\n  # Revenue\n  # Times\n  # SubTimes\n  # Charge\n  # SubCharge\n  # TotalDiscountAmount\n  # TotalDiscountCount\n  # TotalPointThreshold\n  # Frozen\n  # SubFrozen\n  # TotalDepositCount\n  # TotalWithdrawCount\n  # TotalDepositAmount\n  # TotalWithdrawAmount\n  # TotalTransferCharge\n  # TotalDepositCharge\n  # TotalWithdrawCharge\n  # TotalDepositDiscount\n  # RegisterCount\n  # LoginCount\n  # FirstDepositCount\n  # FirstDepositAmount\n  # FirstDepositCharge\n    }\n  }\n}\n  "}

測試結果

{
    "data": {
        "Generalreport": [
            {
                "ChannelName": "VG-棋牌",
                "ChannelCode": "vg_qipai",
                "WalletCode": "vg",
                "Others": "",
                "Items": [
                    {
                        "name": "backendaa",
                        "level": "1",
                        "ordercount": "32",
                        "total": "194",
                        "earning": "161.55",
                        "point": "230.05",
                        "prize": "0"
                    },
                    {
                        "name": "ccp88888",
                        "level": "1",
                        "ordercount": "158",
                        "total": "246276.99",
                        "earning": "12964.84",
                        "point": "245756.39",
                        "prize": "0"
                    },
                    {
                        "name": "test11111",
                        "level": "1",
                        "ordercount": "76",
                        "total": "3790.18",
                        "earning": "809.86",
                        "point": "3773.42",
                        "prize": "0"
                    }
                ],
                "Pager": {
                    "Index": "",
                    "Pages": "",
                    "Size": "",
                    "Total": "3",
                    "TotalOrderCount": "266",
                    "Amount": "250261.17"
                }
            },
            {
                "ChannelName": "福彩/体彩",
                "ChannelCode": "fctc",
                "WalletCode": "cp",
                "Others": "",
                "Items": [
                    {
                        "name": "backendaa",
                        "level": "1",
                        "ordercount": "3",
                        "total": "12",
                        "earning": "12",
                        "point": "12",
                        "prize": "0"
                    },
                    {
                        "name": "ccp88888",
                        "level": "1",
                        "ordercount": "30",
                        "total": "13170",
                        "earning": "13160.15",
                        "point": "13170",
                        "prize": "0"
                    }
                ],
                "Pager": {
                    "Index": "",
                    "Pages": "",
                    "Size": "",
                    "Total": "2",
                    "TotalOrderCount": "33",
                    "Amount": "13182"
                }
            },....


Golang Framework Comparison

時間:

Framework version Time
playlyfe/go-graphql 2018-12-03T01:16:34Z
graph-gophers/graphql-go 2017-04-28T20:40:03Z
samsarahq/thunder 2018-11-28T22:09:52Z
99designs/gqlgen 2018-12-02T22:03:39Z

Summary

Requests/sec
graphql-go 33448.20
graph-gophers 72506.58
thunder 71551.64
gqlgen 99645.21

Without graphql (only gin render json output)

Requests/sec
json without graphql 124663.94

其他

使用公司:https://graphql.org/users/

參考

  1. https://ithelp.ithome.com.tw/articles/10200678
  2. https://ithelp.ithome.com.tw/articles/10188294
  3. https://medium.com/@evenchange4/2018-graphql-漸進式導入的架構-aeb2603f2223
  4. http://graphql.cn/
  5. http://graphql.org/