GraphQL簡單學習-入門
一、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, GraphQLShort和GraphQLChar方便開發者使用。
其中需要注意的是,當對field選用了GraphQLID時,只會接受String和Integer型別的值並將其轉換為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