Tec ‧ 士多開發日記系列:第一篇:GraphQL簡介

Andrew Shek

Andrew Shek

2021-05-21

Tec‧士多開發日記系列:第一篇:GraphQL簡介

Tec‧士多


話說Tecky入面有間「Tec。士多」,大家攞完零食飲品,放低錢入錢箱就可以了。因為小弟最近研究緊GraphQL,所以小弟就諗可唔可以用GraphQL開發一個網上版「Tec。士多」。點知。。。因為是設計過程發現可以加入不同元素入去做示範,例如,Ramda,Prisma, Figma(https://www.figma.com/), storyboard, PostCSS, JS-in-CSS,CSS Variables, Tailwind CSS, PWA, Firebase ,MongoDB, AWS lambda, etc..., 所以小弟打算寫一系列文章透過開發「Tec。士多」示範各種工具。(希望唔會吹大左嘅 ,爛尾🙈。)

IMAGE ALT TEXT

好!故事開始。。。

REST是唔是一種完美既溝通形式?


注意:假設大家對REST有基本概念,我不在此詳述·

無人完美。是Internet世界當然都無完美既溝通形式。REST都會遇到樽頸位。舉個例子,once upon a time,小弟去北海道旅行。當年北海道仍未有4G服務,但是我買錯左一張4G SIM去當地上網。結果只能到用到3G服務。開公司email portal(想當年,小弟非常勤力,心繫公司架😅)非常慢,用FB、WS都不太暢順。原因是。。。

  • 整個web services/web site既設計只是針對相對較大螢幕裝置,例如,Laptop等。螢幕大,可以顯示比較多資訊。隨著手機普及,手機已經取代好多Laptop/Desktop工作同娛樂。因為手機螢幕相對地細,所以用家要看在細螢幕看大量資訊非常困難。

  • 首先,作為一個developers問自己一句,用家是唔是需要是手機上"同一頁,同一時間"看到咁多資訊呢?

  • 如果唔需要,傳輸太多不必要既資訊,由於浪費手機網絡頻寬(Bandwidth),所以未能有效率運用Bandwidth。呢種情況又稱為過度獲取/數據冗餘(over-fetching), 或者,

  • 當然可以拆開幾個web services嚟解決問題。如果我今次需要攞完整資訊 ,就會造成過度請求/請求冗餘(under-fetching)

  • 如果要,就要有效運用Bandwidth,以最快速度下載。因為大量資訊是低頻寬下進行傳輸(一次過),所以手機下載需時。結果影響用家體驗 (User Experiences)。

  • 即是點呀?需要分批同選擇性傳輸資訊。Developers們聽到呢個位開始頭痛了,

  • Developers需要設計另一套API專門俾手機呢類相對較細螢幕裝置了。Developers們聽到呢到更加頭痛,

  • 萬一有一個核心功能需要大更新,例如,每封Email自動加返同事Signature,Developers 需要更新兩套API。工作量大左,出錯機會多左。瞓覺既時間少左,打機既時間少左。

2.jpeg

有無REST以外的選擇呢?


有!GraphQL是其中一解決方法。GraphQL既好處:

  • 使用類似SQL Statement的語法(GraphQL Query Statement)由sever取得資料。由Client決定需要的資料好處是可以是一次HTTP Request取得所需資料,可以避免over-fetching;又可以減少Request次數,提升下載速度。
  • 有效減少backend同frontend耦合性(Coupling)。因為Frontend使用類似SQL Statement語法向Backend取得所需資料,所以Frontend只需用類似SQL Statement形式明確指出需要的資料就可以了。Backend的角色只是整合(Consolidating/Summarizing/Collecting)Client是使用整個應用程式(所有流程)需要的資料供Frontend備用。呢個GraphQL的特色又稱為單一數據源(Single Source of Truth)
  • 支援多種編程語言。因為GraphQL是獨立於任何編程話言。GraphQL本身只是一份規範文件。好多有心人已經開發出不同語言版本。
  • 容易擴展(Scalability)。隨藉微服務(Microservices)盛行,是REST下部署微服務面對一定的難度同挑戰。GraphQL就能夠提供一個彈性介面容易部署微服務。
  • GraphQL提供一個強類型的查詢語言可以每令編程減小出錯。強類型是什麼?是我地既課程會詳細介紹。
  • 不需在多個版本共存進下更新。因為GraphQL可以通知用户個別資訊將會被deprecated(廢棄),所以經過一段時日後就可以從Backend移除相關功能就可以了。

聽落好抽象?即是點呀?看埋個以下簡介再返轉頭看會明白多D。

GraphQL概念入門


注意:假設大家已經知道如何建立Express.js Server,在此不再詳述。

GraphQL Server會以Express.js Server為基礎,所以GraphQL模組(module)是會以中介軟體(middleware)形式放入Express.js Server。簡單講,GraphQL會以一個插件形式夾是Frontend同Backend中間的一個代理人,佢負責解讀同傳遞/交換資訊。

GraphQL.middleware.png

GraphQL Middleware會根據Frontend提供的GraphQL Query Statement@HTTP Request走去不同微服務(Microservices) / 資料庫(Database)/第三方服務提供者(Third-Party Service Providers)取得所需資訊再傳回Frontend。

GraphQL.server.png

GraphQL Query Statement是什麼?簡單講,Frontend會以物件為本(Object Orientation)方式要求Backend提供相關物件數據。用讀取個人資料Web Service做例子示範:

//app.ts (Backend)
//person.ts
class Person{
    private name:string;
    private age: number;
    private email: string;
    constructor(name:string,age:number,email:string){
        this.name = name;
        this.age = age;
        this.email = email;
    }
}

//Express.js Server
//Purpose: get all of person names
app.get("/personName",(req:Request,res:Response)=>{
  const personList:Person[] = [];
  personList.push(new Person("Tom",34,"tom@gmail.com"));
  personList.push(new Person("Peter",30,"peter@gmail.com"));
  personList.push(new Person("Ken",25,"ken@gmail.com"));
  res.json(personList);
})

如果Frontend要request所有Person資料寫法如下:

//index.js
const res = await fetch("/personName");
const people = await res.json(); //[{"Tom",34,"tom@gmail.com"},{"Peter",30,"peter@gmail.com"},{"Ken",25,"ken@gmail.com"}]

以上既web service既原本目的只是攞Person Name,但是連同其他資料(例如,age,email)一拼攞返嚟。造成浪費Bandwidth。當然可以拆開幾個web services嚟解決問題(如下例子),但是如果我需要攞一個完整Person List(包括name,age,email)就會造成過度請求/請求冗餘(under-fetching)。因為起太多HTTP requests,所以造成應用程式反應過慢。

const personList:Person[] = [];
  personList.push(new Person("Tom",34,"tom@gmail.com"));
  personList.push(new Person("Peter",30,"peter@gmail.com"));
  personList.push(new Person("Ken",25,"ken@gmail.com"));

app.get("/personName",(req:Request,res:Response)=>{
  res.json(personList.map(person=>person.name));
});

app.get("/personAge",(req:Request,res:Response)=>{
  res.json(personList.map(person=>person.age));
});

app.get("/personEmail",(req:Request,res:Response)=>{
  res.json(personList.map(person=>person.email));
});

如何用GraphQL解決以上問題?要改以上既Frontend Code由"REST"改成"GraphQL"寫法(GraphQL Server暫時當佢一個black box先,小弟會是一下個篇文章詳細介紹)。

const query = "
{
  person{
    name
  }
}
"
const body = {
	query //qurery:"{person:{name}}"
}
const queryRes = await fetch("/graphql"/*<-graphql server url*/,{
    method: "POST",
    headers:{
        "Content-Type":"application/json"
    },
    body:JSON.stringify(body)
});
const personNameList = await queryRes.json();

發生咩事😵?我看緊咩?小弟逐步拆解佢。首先,const query入面既string是咩意思?

{
  person{
    name
  }
}

回應"GraphQL Query Statment是什麼?"。SQL Statement可以是Select入面選擇讀取咩column。GraphQL當中,Frontend可以用GraphQL Query Statement(即是以上既body內容 = SQL Select Statement) 要求需要的資訊。GraphQL Query Statement會以物件為本的方式要求Backend提供相關物件數據。以上既例子為例,Frontend攞所有person入面name的數據。

如果今次我想攞Person List(包括name,age,email),我只需改一改GraphQL Query Statement就可以。GraphQL Backend不需改任何code(不再需要create 3 個web services,不再擔心over-fetching/under-fetching),只是改動Frontend之GraphQL Query Statement fetch去同一個web service就可得到不同資料。

{
	person{
		name
		age
		email
	}
}

大家開始感受到GraphQL既好處未?

const body = {
	query //qurery:"{person:{name}}"
}
const queryRes = await fetch("/graphql"/*<-graphql server url*/,{
	method: "POST",
  headers:{
    "Content-Type":"application/json"
   },
   body:JSON.stringify(body)
});
const personNameList = await queryRes.json();
/*personNameList = {
    "data": {
    	person:[
        {
          "name": "Tom"
        },
        {
          "name": "Peter"
        },
        {
          "name": "Ken"
        },
    	]
    }
  }
*/    

當砌好GraphQL Query Statement後,只要用fetch傳送GraphQL Query Statement去GraphQL Server就會傳回Frontend指定資訊。大家細心觀察就會發現。。。。其實grahpql既結果會以JSON形式回傳,整個流程同REST無異。

{
    "data": {
    	person:[
        {
          "name": "Tom"
        },
        {
          "name": "Peter"
        },
        {
          "name": "Ken"
        },
    	]
    }
 }
{
    "data": {
    	person:[
        {
          "name": "Tom",
          "age": 34,
          "email":"tom@gamil.com"
        },
        {
          "name": "Peter",
          "age": 30,
          "email": "peter@gmail.com"
        },
        {
          "name": "Ken",
          "age": 25,
          "email":"ken@gmail.com"
        },
    	]
    }
 }

理論部分聽到好悶?我地開始實作環節了🥳!

GraphQL Server 設定


大家可以自己動手起左 Express.js Server先,不在此詳述。

安裝 GraphQL模組,請在VS Code下的Terminal打:

yarn add graphql @types/graphql apollo-server

坊間有好多第三方GraphQL模組,以Apollo GraphQL最為人熟悉。Apollo GraphQL提供好多方便工具快速開發GraphQL Server同測試。

補充: 其實可以用GraphQL 官方模組開發Server同測試,但是開發時間比較長。小弟自己玩既時候都是直接GraphQL 官方模組開發。因為想左俾大家容易上手,所以選擇Apollo GraphQL做示範。此外用GraphQL 官方模組既好處是開發某D功能彈性比較大,例如,Payment,Login等。。。

GraphQL Server 實作


是Express.js入面既entry point file,例如,app.ts入以下既code:

//直接import graphql server class,instance之後直接起動。
//typeDefs,resolvers<-- 會是下一篇文章詳情講解
import {ApolloServer,gql} from 'apollo-server-express';
import Express from 'express';

class Person{
    public name:string;
    public age: number;
    public email: string;
    constructor(name:string,age:number,email:string){
        this.name = name;
        this.age = age;
        this.email = email;
    }
}

const personList:Person[] = [];
personList.push(new Person("Tom",34,"tom@gmail.com"));
personList.push(new Person("Peter",30,"peter@gmail.com"));
personList.push(new Person("Ken",25,"ken@gmail.com"));

const app = Express();
const typeDefs = gql`
    type Person{
        name:String,
        age:Int,
        email:String
    }
    type Query{
        person:[Person]
    }
`;
const resolvers= {
    Query:{person:()=>personList},
    Person:{
        name:(parent:Person)=>parent.name,
        age:(parent:Person)=>parent.age,
        email:(parent:Person)=>parent.email
    }
};

const server = new ApolloServer({typeDefs,resolvers});
server.applyMiddleware({app,path:'/graphql'}); //<-- 記得是"GraphQL概念入門"開頭提過Middleware既概念嗎?
app.listen(4000,()=>{
    console.log("GraphQL Server is started at http://localhost:4000/graphql");
});

可以起動台Server了🥳。

node .

GraphQL Server 測試


GraphQL模組提供一個測試平台俾Developers做測試。如果是Apollo GraphQL,測試平台叫GraphQL Playground。如果GraphQL 官方模組,測試平台叫iGraphQL。是Browser打以下URL:

http://localhost:4000/graphql

是平台左邊Panel打:

{
  person{
    name
  }
}

👆🏻還記得佢嗎?

是右邊Panel會出現結果。

playground.result.1.pngplayground.result.2.png

打開右邊Panel可以看到呢台GraphQL Server可以提供的數據。

playground.doc.png

下一篇文章詳細介紹GraphQL Server 實作方法。

Tec。記士 Demo Source

References


留言

延伸閱讀

零基礎.10分鐘輕鬆製作STEM教材

0成本!分析「保就業計劃」數據,超方便隨時網上更新分享分析結果

分析「保就業計劃」數據,超方便隨時網上更新分享分析結果 (Microsoft Power BI 版本)

常見的 Bootstrap 新手中伏位:從手帶 app (居安抗疫) 下載頁一齊睇

Tec ‧ 士多開發日記系列:第二篇:實作GraphQL Server(基礎入門)- Part 1