跳至主內容

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

Tec ‧ 士多開發日記系列:第一篇:GraphQL簡介
Andrew Shek
2021-05-21

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。士多」示範各種工具。(希望唔會吹大左嘅 ,爛尾🙈。)

按些觀看 Tec 士多 Demo

好!故事開始。。。

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。工作量大左,出錯機會多左。瞓覺既時間少左,打機既時間少左。

網絡迷因網:坐在電腦面前思考

有無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 網絡流程

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

GraphQL Server 內部流程

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會出現結果。

GraphQL Playground 結果截圖示範 1GraphQL Playground 結果截圖示範 2

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

GraphQL Playground 連 Schema 的結果

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

Tec。記士 Demo Source

References


留言

閱讀更多

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

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

常見的 Bootstrap 新手中伏位:從手帶 app (居安抗疫) 下載頁一齊睇
Alex Lau 劉光曆
2020-03-26

筆者一看到居安抗疫這個網站就覺得排位怪怪的,打開後發現,充滿著常見的 Bootstrap 新手中伏位!讓我們一起來看一看!


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

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

零基礎.10分鐘輕鬆製作STEM教材
Andrew Shek
2020-07-03

因為疫情學校停課,造就「網上教學」興起。但是教師們多數只是用視訊會議軟體進行網上「授課」,但是並不是真正既「網上教學」。根據範式轉移:網上教學的迷思,eLearning(網上教學)應該是:


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

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

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

根據工貿署2020年3月數字,中小企是佔本港商業單位總數98%以上,但是從「保就業」數據當中得知,中小企只是佔13.6%。可以等第二期結果出爐再統計一下。從數據分析得知,就算派得最多錢唔代表可保就業。成功申請的中小企比較小;大部分資助落入0人企業和微型企業手上,造成分配不公。成效成疑。同時從中反映出成個計畫審批唔透明,無完善監管機制阻止濫用。政府需要好好檢討完善成個計畫。


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

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

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

根據工貿署2020年3月數字,中小企是佔本港商業單位總數98%以上,但是從「保就業」數據當中得知,中小企只是佔13.6%。可以等第二期結果出爐再統計一下。從數據分析得知,就算派得最多錢唔代表可保就業。成功申請的中小企比較小;大部分資助落入0人企業和微型企業手上,造成分配不公。成效成疑。同時從中反映出成個計畫審批唔透明,無完善監管機制阻止濫用。政府需要好好檢討完善成個計畫。


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

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

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

上一編Blog已經詳細講解GraphQL Frontend既實作方法。今編Blog就深入講解GraphQL Server運作模式及如何實作。開始講解GraphQL Server運作原理及實作之前,有三個概念一定要了解左先,模式(Schema)、解析器(Resolver)及資訊源(Data Sources)。


索取課程大綱
提交後, 請檢查你的電郵
hello@tecky.iot.me/tecky_hub+852 9725 6400
green_org
商界展關懷 2019-2022
英國頒證機構 TQUK 認可中心
aws_partner
薯片叔叔共創社 重塑教育挑戰大獎
B Corp™ 認證共益企業
無障礙網頁內容指引 (WCAG) 2.1 AA 級
香港無障礙網頁 金獎
© 2024 Tecky Academy Limited