1. 程式人生 > >GraphQL簡單學習-入門

GraphQL簡單學習-入門

浪費了“黃金五年”的Java程式設計師,還有救嗎? >>>   

一、GraphQL是什麼

GraphQL是Facebook在2012年建立、2015年形成規範的一種應用層查詢語言,它具有很多優點:客戶端可以自定義查詢語句,通過自定義不僅提高了靈活性,而且服務端只返回客戶端所需要的資料,減少網路的開銷,提高了效能;服務端收到客戶端的請求,首先做型別檢查,及時反饋,而且更加安全;自動生成文件,降低維護成本;服務端通過新增欄位,Deprecated欄位,避免版本的繁雜和紊亂。然而上面所述也都是Rest API弊端,GraphQL卻能很好的解決他們。

GraphQL不與任何資料庫或儲存引擎繫結,而且已經有很多程式語言都提供了類庫對GraphQL的支援,如NodeJs、Java、PHP、Ruby、Python、Scala、Clojure、Go、Net、Elixir、Swift等。GraphQL更關注的是前端互動,因此你可以在你現有的系統上做改造

二、GraphQL的查詢

Graph的查詢語句非常類似JSON,如客戶端的查詢語句

{
  user{
    name
    sex
  }
}

上述語句的目的是查詢使用者資訊的name和sex欄位資料,服務端返回結果如

{
  "data": {
    "user": {
      "name": "wind",
      "sex": "男"
    }
  }
}

當客戶端使用如下查詢語句時

{
  user{
    name
    intro
  }
}

服務端返回的結果如


  "data": {
    "user": {
      "name": "windhan",
      "intro": "程式設計師,Java程式設計師,Web前端"
    }
  }
}

這樣看-->是不是客戶端完全自由了,可以根據自己特定的業務來獲取資料。

三、Schema

GraphQL中非常重要的一個概念是Schema,如果你瞭解XML Schema的話,就非常容易理解GraphQL的Schema了,Schema由服務端來定義,用於定義API介面,並依靠Schema來生成文件以及對客戶端請求進行校驗。Schema只是一個概念,它是由各種資料型別及其欄位組成,而每個型別的每個欄位都有相應的函式來返回資料,這是其靈活的關鍵所在,如Schema

type User{
    name: String
    sex: String
    intro: String
}
type Query {
    user:User
}

上面定義了兩個型別,User和Query:User有三個欄位分別是name、sex和intro;Query是個特殊的型別,用於查詢資料,內含user欄位;這些型別中的欄位都有自動生成的函式來返回資料,函式類似(JS型別):

function User_name(user) {
  return user.getName();
}
function User_sex(user) {
  return user.getSex();
}
function User_intro(user) {
  return user.getIntro();
}
function Query_user(request) {
  return request.user;
}

因此當服務端接收到客戶端的如下查詢語句

{
  user{
    name
    sex
  }
}

會呼叫上面的函式組合來返回資料。下面我們一下JAVA(springboot)為例做一個demo: https://my.oschina.net/hanchao/blog/3014079

四、資料型別

GraphQL是應用層查詢語言,它有自己的資料型別,用來描述資料,比如標量型別、集合型別、Object型別、列舉等,甚至有介面型別。

1.標量型別(Scala)

標量型別是不能包含子欄位,主要有如下型別:

  • Int: 有符號的32位整型
  • Float: 有符號的雙精度浮點型
  • String: UTF‐8字元序列
  • Boolean: 布林型
  • ID: 常用於獲取資料的唯一標誌,或快取的鍵值,它也會被序列化為String,但可讀性差。

以上的型別是GraphQL規範的基本型別,每個規範的實現也可以有自定以標量,如 scala Date ,但它必須能夠序列和反序列化,至於如何定於取決於具體的規範實現。

graphql-java中的ScalarType

在graphql-java中,除了GraphQL文件中說明的最基本的型別 GraphQLInt, GraphQLFloat, GraphQLString, GraphQLBoolean, GraphQLID之外,還包含了GraphQLLong, GraphQLBigInteger, GraphQLBigDecimal, GraphQLByte, GraphQLShortGraphQLChar方便開發者使用。

其中需要注意的是,當對field選用了GraphQLID時,只會接受StringInteger型別的值並將其轉換為String傳遞出去, 而通常資料庫預設定義的id是Long,如果用GraphQLID的話可能會出錯。

2.列舉(Enum)

enum Unit {
	MM
	mm
}

MM代表米做單位,mm程式碼毫米做單位。

3.物件(Ojbect)

組成Schema最常用的是物件型別,它包含各種欄位,定義如:

type User{
  name: String
  sex: String
  intro: String
}

以上定義了一個User物件,包含name(名字)、sex(性別)、intro(介紹)屬性欄位,而這些屬性欄位都是標量String型別,當然屬性也可以是物件型別。

4.集合(List)

GraphQL規範中的集合只有List一種,它是有序的用 [] 表示,如

type User{
  name: String
  sex: String
  intro: String
  skills: [String]
}

skills(技能)就是一個list集合,其實更像是一個數組。

5.非空/Null(Non-Null)

在型別後使用 ! 宣告 非空/Null,如

type Query {
  user(id:Int!):User
}
type User{
  name: String!
  sex: String
  intro: String
  skills: [String]!
}

以上定義了查詢介面user,其中引數id為必須提供,如果你使用如下查詢語句

query{
  user {
    name
    sex
    intro
  }
}

則會反饋異常

{
  "errors": [
    {
      "message": "Field \"user\" argument \"id\" of type \"Int!\" is required but not provided.",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ]
    }
  ]
}

而實體型別User的name為非null,一但服務端返回的資料name為null,則反饋為

{
  "data": {
    "user": null
  },
  "errors": [
    {
      "message": "Cannot return null for non-nullable field User.name.",
      "locations": [
        {
          "line": 3,
          "column": 5
        }
      ],
      "path": [
        "user",
        "name"
      ]
    }
  ]
}

然而需要注意區別 skills: [String]! 與 skills: [String!] ,前者是集合不能為null,後者是集合元素不能為null。

6.模式(Schema)

GraphQL的Schema用於生成文件、格式定義與校驗等,除了自定義的型別如上文中的User,還有兩個特殊的型別Query(查詢)和Mutation(維護)。如增查改刪(CRUD),增改刪屬於後者,查屬於前者。

schema {
  query: Query
  mutation: Mutation
}

一個Schema可以沒有mutaion,但必須有query。

7.查詢(Query)

Query目的是獲取資料。格式:

query queryName{
	operation
}

query userQuery{
  user(id:0){
    name
    sex
    stature
  }
  users{
    name
    sex
    stature
  }
}

實際上如果只有一個查詢,“query”“queryName”都可以省略,如

{
  user(id:0){
    name
    sex
    stature
  }
  users{
    name
    sex
    stature
  }
}

8.維護(Mutation)

Mutataion是用來維護資料的,格式和查詢類似

mutation mutationName{
	operation
}

如:

mutation{
  addUser(name:"testUser",sex:"男",intro:"簡介",skills:[]){
    name
    sex
    intro
  }
}

由於Schema可以沒有Mutation,但必須有Query,因此mutaion關鍵字是不可以省略的,否則被認為是Query而找不到操作名。

9.引數(Argument)

客戶端的查詢語句中物件和欄位都可以傳入引數,方便精確控制服務返回結果,查詢語句如

{
  user(id:1) {
    name
    sex
    intro
    skills
    stature(unit:MM)
  }
}

服務返回資料

{
  "data": {
    "user": {
      "name": "James",
      "sex": "男",
      "intro": "xxx的英文名",
      "skills": [
        "Linux",
        "Java",
        "nodeJs",
        "前端"
      ],
      "stature": 1.8
    }
  }
}

服務端是靠resolver來解析實現的

10.指令(Directive)

客戶端可以使用兩種指令skip和include,可以用與欄位(field)、片段(fragment),格式和含義如下:

  • field/fragment @skip(if: $isTrue) 當$isTrue為真(true)時不查詢field或不使用fragment;
  • field/fragment @include(if: $isTrue) 當$isTrue為真(true)時查詢field或使用fragment;

而引數變數是通過query或mutation傳遞的;變數形如$withName:Boolean!,以$開頭,以型別結尾,型別必須是標量(scalar)、列舉(enum)或輸入型別(input)。如query為

query(
  $noWithDog:Boolean!,
  $withName:Boolean!,
  $withFish:Boolean!
){
  animals{
    name @include(if:$withName)
    ... dogQuery @skip(if:$noWithDog)
    ... on Fish @include(if:$withFish){
      tailColor
    }
  }
}
fragment dogQuery on Dog{
  legs 
}

在query定義中分別定義了$noWithDog(是否帶著狗狗)、$withName(是否帶著Name)、$withFish(是否帶著魚兒)變數,傳入的引數變數為

{
  "noWithDog": true,
  "withName": true,
  "withFish": true
}

查詢的結果如

{
  "data": {
    "animals": [
      {
        "name": "dog"
      },
      {
        "name": "fish",
        "tailColor": "red"
      }
    ]
  }
}

至於如何傳遞引數,可以參考:https://my.oschina.net/hanchao/blog/3014116#h3_7

如果引數變數傳給非空的欄位,那麼引數變數也必須是非空型別,否則可以允許為空,允許為空是屬於可選引數。而且引數變數可以有預設值值,如

query Animal($type: String = "dog") {
  animal(type: $type) {
    name
  }
}

11.輸入(Input)

上面的引數(Argument)我們使用是標量,但當使用Mutation來更新資料時,你可能更喜歡傳入一個複雜的實體Object,GraphQL使用input關鍵字來到定義輸入型別,不直接用Object是為了避開迴圈引用、介面或聯合等一些不可控的麻煩,特別是input型別不能像Object那樣帶引數的。如

input UserInput {
  name: String!
  sex: String
  intro: String
  skills: [String]!
}

定義了一個輸入型別UserInput,欄位有name、sex、intro和skills.

12.介面(Interface)

類似其他語言,GraphQL也有介面的概念,方便查詢時返回統一型別,介面是抽象的資料型別,因此只有介面的實現才有意義,如

interface Animal{
  name: String!
}
type Dog implements Animal{
  name: String!
  legs: Int!
}
type Fish implements Animal{
  name: String!
  tailColor: String!
}

上面的介面Animal有兩個實現:Dog和Fish,它們都有公共欄位name。服務端的查詢Schema可以是類似如下的定義

type Query {
  animals:[Animal]!
}

而客戶端的查詢需要使用下面的Fragment,稍後展示。

13.聯合(Union)

聯合查詢,類似介面式的組合,但不要求有公共欄位,使用union關鍵字來定義,如

type Dog{
  chinaName: String!
  legs: Int!
}
type Fish{
  englishName: String!
  tailColor: String!
}
union Animal = Dog | Fish

則Animal可以是Dog也可以是Fish,服務端定義查詢可以和上面的介面相同,客戶端查詢也需要使用Fragment,稍後展示。

14.片段(Fragment)

Fragment分為內聯和外聯,兩種都是用於選擇主體,而後者還可以共用程式碼。如上面的介面示例,Dog和Fish都是介面Animal的實現,有介面的公共欄位name,簡單的內聯

{
  animals{
    name
    ... on Dog{
      legs
    }
    ... on Fish{
      tailColor
    }
  }
  animalSearch(text:"dog"){
    name
    ... on Dog{
      legs
    }
    ... on Fish{
      tailColor
    }
  }
}

如果是Dog則查詢legs欄位,如果是Fish則查詢tailColor欄位,這種內聯如果單個查詢則比較方便,但多個查詢則使用外聯更簡潔,如下

{
  animals{
    ... animalName
    ... dogLegs
    ... fishTail
  }
  animalSearch(text:"dog"){
    ... animalName
    ... dogLegs
    ... fishTail
  }
}

fragment animalName on Animal {
  name
}
fragment dogLegs on Dog{
  legs
}
fragment fishTail on Fish{
  tailColor
}

在聯合(union)查詢,因為沒有公共欄位,使用fragment示例如下

{
  animals{
    ... on Dog{
      chinaName
      legs
    }
    ... on Fish{
      englishName
      tailColor
    }
  }
}

15.別名(Alias)

GraphQL支援別名命名,這對於查詢多個雷同的結果非常有用,格式為 aliasName: orginName 如

{
  james:user(id:1) {
    name
    sex
    intro
    skills
    MM:stature(unit:MM)
    mm:stature(unit:mm)
  }
  jerry:user(id:0) {
    name
    sex
    intro
    skills
    MM:stature(unit:MM)
    mm:stature(unit:mm)
  }
}

以上分別把id為1、2的分別給予別名為james、jerry,以米(MM)、毫米(mm)為單位的身高分別給予別名MM、mm,服務端返回的資料如

{
  "data": {
    "james": {
      "name": "James",
      "sex": "男",
      "intro": "xiaozhao的英文名",
      "skills": [
        "Linux",
        "Java",
        "nodeJs",
        "前端"
      ],
      "MM": 1.8,
      "mm": 180
    },
    "jerry": {
      "name": "jerry",
      "sex": "男",
      "intro": "博主,專注於Linux,Java,nodeJs,Web前端:Html5,JavaScript,CSS3",
      "skills": [
        "Linux",
        "Java",
        "nodeJs",
        "前端"
      ],
      "MM": 1.8,
      "mm": 180
    }
  }
}

參考文章

加強理解:https://segmentfault.com/a/1190000014829295

GraphQL之Java實戰入門:http://www.zhaiqianfeng.com/2017/06/learn-graphql-ac