มาทำความรู้จักกับ GraphQL กันเถอะ

foursy p.
Nextzy
Published in
5 min readAug 2, 2019

หลายคนอาจจะเคยได้ยินเกี่ยวกับ GraphQL กันมาแล้วบ้างในโลกของการทำงานร่วมกับ API บ้างก็ว่ามันเกิดมาเพื่อฆ่า REST API และทำให้การจัดการข้อมูลระหว่าง Client และ Server มีประสิทธิภาพมากขึ้น แต่ถ้าหากใครยังไม่เคยรู้จัก ไม่ต้องห่วงเราจะมาทำความรู้จัก GraphQL ไปพร้อมๆกัน โกกกกก!

GraphQL คืออะไร ?

ถ้าเราเข้าไปที่เว็บ https://graphql.org/ โดยตรง เราจะได้คำนิยามว่า

“ GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. ”

พูดง่ายๆ ก็คือ มันคือภาษาที่ใช้ในการสืบค้นข้อมูลจาก API เหมือนเป็นตัวกลางที่ใช้ในการจัดการข้อมูลต่างๆ เพื่อให้เราได้ข้อมูลที่มีประสิทธิภาพมากขึ้น มีประสิทธิภาพยังไงเดี๋ยวเราจะมาอธิบายกันในหัวข้อถัดไป

GraphQL ถูกสร้างขึ้นโดย Facebook โดยเค้าเคลมว่าเป็น query language ที่เข้าใจง่าย ใช้งานง่าย ไม่ซับซ้อน และให้ผลลัพธ์ได้ตรงตามความต้องการของผู้ใช้งาน ปัจจุบันถูกนำไปใช้งานอย่างแพร่หลายในบริษัทต่างๆ เช่น Facebook (ก็สร้างขึ้นมาเองอะนะ), Twitter, Pinterest, Github, Paypal, Atlassion, Dailymotion และอื่นๆ

ซึ่งหลังจากเราส่ง request เพื่อไปดึงข้อมูลมาจาก API ผลลัพธ์ที่ได้จะถูกส่งกลับมาในรูปแบบของ JSON (JavaScript Object Notation) หากใครที่รู้จัก data format ประเภทนี้แล้วสามารถข้ามพาร์ทนี้ไปได้เลย

{
"page":1,
"per_page":2,
"total":12,
"total_pages":6,
"data":[
{
"id":1,
"first_name":"George",
"last_name":"Bluth", "avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/calebogden/128.jpg"
},
{
"id":2,
"first_name":"Janet",
"last_name":"Weaver", "avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/josephstein/128.jpg"
}
]
}

ด้านบนคือตัวอย่างของ JSON Object ที่จะถูกส่งกลับมา โดยข้อมูลแต่ละ element จะแบ่งออกเป็น 2 ส่วน คือ “name” : value จากตัวอย่างข้างบนจะเห็นว่าข้อมูลที่เราได้มามี เลขหน้า (page number), จำนวนข้อมูลในแต่ละหน้า, จำนวนข้อมูลทั้งหมด, จำนวนหน้าทั้งหมด และสุดท้ายคือ array ของข้อมูลในหน้านี้ (page 1) ซึ่งก็จะประกอบไปด้วย 2 JSON Objects ย่อยๆ ที่แสดงข้อมูลของ user แต่ละคน

แล้ว GraphQL มันดีกว่า RESTful API ยังไงหล่ะ ?

หลังจากรู้จักไปคร่าวๆ แล้วว่ามันคืออะไร ตอนนี้เราจะมาทำความเข้าใจกันว่า แล้วทำไมเราถึงต้องใช้ GraphQL ด้วยหล่ะ? ที่บอกกันว่ามันดีกว่า REST นี่มันดีตรงไหน? แล้วทำไมข้อมูลที่ได้มาถึงมีประสิทธิภาพนะ?

เกิดปัญหาอะไรขึ้นกับการขอข้อมูลแบบเดิม

ยกตัวอย่างเช่น เรายิง request ไปเพื่อขอข้อมูลเกี่ยวกับหนังสือทั้งหมดในระบบ ได้ผลลัพธ์กลับมาดังนี้

{
books: [
{
bookId: 1,
title: 'Lost in the sky',
edition: 4,
authorId: 1,
published_date: '2017-07-14'
}, {
bookId: 2,
title: 'You are my meteorite',
edition: 6,
authorId: 2
published_date: '2018-11-09'
}
]
}

โอเค เราได้ข้อมูลของหนังสือทั้งหมดตามที่เราต้องการมาแล้ว ส่งข้อมูลไปใช้ต่อได้ ต่อมาอยู่ๆ ลูกค้าเดินกลับมาบอกเราว่า

“พี่อยากรู้ข้อมูลเกี่ยวกับคนเขียนหนังสือด้วย เพิ่มข้อมูลเกี่ยวกับคนเขียนให้หน่อย”

สิ่งที่เราต้องทำก็คือเอา authorId ที่เราได้มาจากผลลัพธ์ด้านบนไปค้นหาข้อมูลของนักเขียนด้วยการยิง request พร้อม id ไปอีกที ถ้าเรามีหนังสือ 10 เล่ม นักเขียน 10 คน เราก็ต้องส่ง request ไป 10 รอบ สิ้นเปลือง resource ชะมัดเลยยยย

อีกหนึ่งปัญหาที่ทำให้ประสิทธิภาพของข้อมูลลดลง คือ ข้อมูลที่คืนกลับมานั้นมีมากเกินกว่าความต้องการของเรา ยกตัวอย่างเช่น ลูกค้าเดินกลับมาบอกเราอีกทีว่า

“น้อง edition กับ published date พี่ไม่ได้ใช้แล้วอ่ะ เอาออกให้พี่หน่อย”

ซวยหล่ะสิคราวเนี้ย จะทำไงได้ในเมื่อ API มันคืนค่าข้อมูลมาให้แบบนี้อ่ะ เราก็จะต้องคลานเข่าไปไหว้วานให้ backend ช่วยเปลี่ยน API ให้ใหม่ แล้วถ้าเกิดอนาคตลูกค้าเปลี่ยนใจอีกทีหล่ะ เปลี่ยนไปเปลี่ยนมาโดน backend มองแรงแน่ๆ

แก้ปัญหาที่ว่ามานี้ด้วย GraphQL สิจ้ะ

ปัญหาข้างต้นจะหายวับไปกับตาหากเราเปลี่ยนมาใช้ GraphQL จากสถานการณ์ก่อนหน้า ถ้าต้องการข้อมูลนักเขียนด้วยหรอ หึ ไม่ต้องส่ง request ไปหลายรอบให้ยุ่งยาก

query {
getAllBooks {
bookId
title
edition
author {
authorId
name
age
}
published_date
}
}

จาก query ข้างต้น เราจะได้ผลลัพธ์ดังนี้

{
"data": {
"getAllBooks": [
{
"bookId": 1,
"title": "Lost in the sky",
"edition": 4,
"author": {
"authorId": 1,
"name": "Marry Alison",
"age": 34
},
"published_date": "2017-07-14"
},
{
"bookId": 2,
"title": "You are my meteorite",
"edition": 6,
"author": {
"authorId": 2,
"name": "Spencer Montgomery",
"age": 29
},
"published_date": "2018-11-09"
}
]
}
}

Tada! เราได้มาทั้งข้อมูลหนังสือทั้งหมด และข้อมูลของคนเขียนหนังสือแต่ละเล่มด้วยการส่ง request เพียงครั้งเดียว สุดยอดไปเลย แล้วถ้าสมมติเราไม่ได้ต้องการข้อมูลทั้งหมดแต่ต้องการแค่บางตัวแบบที่กล่าวถึงก่อนหน้านี้หล่ะ ง่ายมากก็ส่ง request ไปแบบนี้ได้เลย

query {
getAllBooks {
bookId
title
author {
name
}
}
}

มาดูผลลัพธ์กันโลดดดด

{
"data": {
"getAllBooks": [
{
"bookId": 1,
"title": "Lost in the sky",
"author": {
"name": "Marry Alison"
}
},
{
"bookId": 2,
"title": "You are my meteorite",
"author": {
"name": "Spencer Montgomery"
}
}
]
}
}

จะเห็นได้ว่าการใช้ GraphQL เพื่อ query แค่ข้อมูลที่เราต้องการใช้ออกมา ทำให้เราสามารถปรับเปลี่ยน customize รูปแบบของ data ที่เราจะนำไปใช้ต่อได้ตามต้องการและไม่ยุ่งยาก นี่แหละที่ทำให้ข้อมูลที่ได้จากฐานข้อมูลมีประสิทธิภาพมากขึ้น

ทีนี้เราก็จะไม่ต้องปวดหัวเวลาที่เราต้องการใช้ข้อมูลในรูปแบบต่างกันไปในสถานการณ์ต่างๆ เช่น บน Web platform เราต้องการใช้ข้อมูลทั้งหมด ในขณะที่บน Mobile platform ที่มี space จำกัด เราอาจจะต้องการใช้ข้อมูลเพียงบางส่วนเท่านั้น!

การสืบค้น (Query) และเปลี่ยนแปลง (Mutate) ข้อมูล

Action หลักๆ ที่เราสามารถทำได้มี 2 อย่าง คือ
1) สืบค้นข้อมูล (Query data)
2) เปลี่ยนแปลงข้อมูล (Mutate data)
ในพาร์ทนี้เราจะยกตัวอย่างให้พอเห็นภาพของการทำงานของ GraphQL ร่วมกับ API

สมมติว่าเรากำลังพัฒนา Application ที่ใช้ในการจัดาร Conference ต่างๆ ที่เกิดขึ้นโดยมี features หลักๆ คือ
▪▫ สร้าง Attendee
▪▫ สร้าง Conference
▪▫ เรียกดูข้อมูล Attendee แต่ละคน
▪▫ เรียกดูข้อมูล Conference แต่ละอัน
▪▫ อัพเดตข้อมูล Conference
▪▫ ลบ Attendee ออกจากระบบ
▪▫ ลบ Conference ออกจากระบบ
▪▫ เพิ่มและลบ Attendee เข้า/ออกจาก Conferece ที่มีอยู่ได้

Querying Data

ถ้าอ่านมาถึงตรงนี้ก็น่าจะพอรู้กันแล้วจากตัวอย่างที่ผ่านๆ มาข้างบน ว่ารูปร่างหน้าตาของ query request เป็นยังไง มาลองดูกันอีกทีโดย based on สถานการณ์ข้างต้น

สมมติว่าเรามีข้อมูล Conference ในระบบอยู่แล้ว แล้วเราอยากจะดูว่ามีอะไรบ้าง

query {
allConferences {
attendees {
createdAt
id
name
updatedAt
}
city
createdAt
id
name
updatedAt
year
}
}

เมื่อยิงคำสั่งนี้ไป เราก็จะได้ข้อมูล JSON ที่มีรายละเอียดตามนี้มา
ต่อไปเราจะเอาแค่บาง fields มาแล้วกัน ก็อยากได้แค่ไอดีงาน, ชื่องาน, ปีที่จัด, สถานที่ที่จัด และข้อมูลของผู้ร่วมงาน (ไอดีกับชื่อก็พอแล้ว!) นี่หน่า

query {
allConferences {
attendees {
id
name
}
city
id
name
year
}
}

และนี่คือผลลัพธ์ของเราจาก request ที่เรา customize (ขออภัยยาวไปนิสสส)

{
"data": {
"allConferences": [
{
"city": "San Jose, California",
"name": "2019 Apple WWDC",
"year": "2019",
"id": "cjys91fbx0kur0186xewk2diw",
"attendees": [
{
"id": "cjys8vf170kfn01254qamur1x",
"name": "John Mayer"
}
]
},
{
"city": "KOSMOS Berlin",
"name": "GraphQL Conf 2019",
"year": "2019",
"id": "cjys933dt0lp401360n3hom6o",
"attendees": [
{
"id": "cjys8xdau0l4p0181i8c0jd6u",
"name": "Troye Sivan"
}
]
},
{
"city": "Santa Clara, California",
"name": "IoT World Hackathon 2019",
"year": "2019",
"id": "cjys95apl0lxq0136iocahynm",
"attendees": [
{
"id": "cjys8vzdb0kif0125d2w271hf",
"name": "Daniel Caesar"
},
{
"id": "cjys8wj6z0km20117zlte581a",
"name": "Paul Jason Klein"
}
]
}
]
}
}

จะเห็นว่าเรามี Conference ทั้งหมด 3 งาน สองงานแรกมี Attendee 1 คน ส่วนงานสุดท้ายมี 2 คน ผลลัพธ์จึงออกมาเป็น array ของข้อมูล Attendee แต่ละคน ง่ายมาก!

ต่อไปหาก query request ที่เราต้องการส่งไปนั้นจำเป็นต้องมีข้อมูลหรือ variable ส่งไปด้วย เช่น เราอยากจะได้ข้อมูลของ Conference ของปีอื่นๆ ที่ไม่ใช่ปี 2019

query {
allConferences(filter: { year_not: "2019" }) {
...
}

ผลลัพธ์ที่เราได้ออกมาจะเป็น empty array เนื่องจาก เราบอกไปใน request ว่าเราต้องการทุก Conference โดยใช้ filter “ที่ไม่ได้จัดในปี 2019(ถ้าย้อนไปดู ทุกงานจะจัดในปี 2019 ทั้งหมด)

หรือถ้าเราอยากจะได้ข้อมูลของ Conference อันนึงแบบเจาะจง เราก็จำเป็นจะต้องส่ง Conference id ไปด้วยเพื่อที่จะได้หยิบข้อมูลมาถูกอัน โดยการทำแบบนี้

query {
conference(id: "cjys95apl0lxq0136iocahynm") {
...
}

Mutating Data

นอกจากที่เราจะเรียกดูข้อมูลต่างๆ ได้แล้วนั้น เรายังสามารถเรียกใช้ฟังก์ชั่นที่มีเพื่อเปลี่ยนแปลงข้อมูลในฐานข้อมูลได้ด้วย โดยการเปลี่ยนแปลงข้อมูลจะเรียกว่าการ Mutate ผ่านการใช้ชุดคำสั่งสำหรับเปลี่ยนแปลงข้อมูลหรือ Mutations

ยกตัวอย่างเช่น ผู้ใช้งานชื่อ Paul ต้องการจะเข้าร่วม GraphQL Conf 2019 โดยการกดปุ่ม ‘Attend this conference’ เมื่อเค้ากดปุ่ม เราจะต้องส่ง Mutation request ไปทันทีเพื่อบอกว่า เราต้องเพิ่ม Paul เป็นผู้ร่วมงานของ GraphQL Conf 2019 นะ ข้อมูลที่ถูกเปลี่ยนแปลง (ในที่นี้คือเพิ่มเข้าไป) ก็จะถูกเก็บเข้าในฐานข้อมูลอัตโนมัติ

มาดูหน้าตาของการส่ง Mutation กัน

mutation {
addToAttendees(attendeesAttendeeId: "cjys8wj6z0km20117zlte581a", conferencesConferenceId: "cjys933dt0lp401360n3hom6o") {
conferencesConference {
name
}
attendeesAttendee {
name
}
}
}

สิ่งที่เราทำคือ เราบอก API ว่าเราต้องการ mutate data ด้วยคำสั่ง addToAttendees โดยที่เราจะเพิ่มผู้ใช้งานที่มี id นี้ (id ของ Paul) เป็นผู้เข้าร่วม Conference ที่มี id นี้ (id ของ GraphQL Conf 2019) แล้วให้มัน return ค่าเป็นชื่อของคนกับงานออกมาให้เราดูเมื่อทำเสร็จแล้ว

แล้วถ้าอยู่ๆ Paul บอกว่า “ไม่เอาอ่ะไม่อยากไปและ ยกเลิกดีกว่า” Paul จึงไปกดปุ่ม ‘Cancel my attendance’ สิ่งที่เราต้องทำก็คือส่ง mutation request เพื่อไปบอกว่า Paul เปลี่ยนใจไม่ไปแล้วนะ ลบเค้าออกจากผู้เข้าร่วมงานนี้ได้แล้ว!

mutation {
removeFromAttendees(attendeesAttendeeId: "cjys8wj6z0km20117zlte581a") {
conferencesConference {
name
}
attendeesAttendee {
name
}
}
}

สิ่งที่เราจะได้กลับมาก็คือออออออออ Error!!! ทำไมหล่ะ เราทำอัลไลผิดกันนะ ?

ยังมีอีกหนึ่งสิ่งสำคัญที่ควรรู้คือ หากเราดูใน schema ของ API ที่เราใช้ (ปุ่มด้านข้างที่ยื่นออกมาบริเวณริมบนขวา) เราจะพบว่ามีรายละเอียดเกี่ยวกับคำสั่งต่างๆ ที่เราสามารถใช้งานได้ เมื่อเรากดเข้าไปดูที่ removeFromAttendees จะพบว่า

ถ้าสังเกตบริเวณที่มี Highlight สีแดงจะเห็น Argument ที่สามารถใส่ได้อยู่ 2 ตัวคือ attendeesAttendeeId และ conferencesConferenceId โดยใช้ ID ซึ่งจะมีเครื่องหมาย ! กำกับอยู่ด้านหลัง นั่นหมายถึง เราจำเป็นจะต้องใส่ข้อมูลนี้ด้วยทุกครั้ง หากไม่ใส่เราจะไม่สามารถส่ง request นั้นได้ และไม่ใช่แค่ mutations แต่ยังรวมไปถึง queries ด้วย อย่างในตัวอย่างข้างต้นหากเราไม่ระบุ conferencesConferenceId ระบบก็คงจะงงๆ ว่า แล้วแกจะให้ชั้นลบ Paul ออกจากไหนหละจ๊ะ นั่นเอง

ดังนั้น รูปร่างหน้าตาของ mutation request ที่ถูกต้องจึงควรเป็นดังนี้

mutation {
removeFromAttendees(attendeesAttendeeId: "cjys8wj6z0km20117zlte581a", conferencesConferenceId: "cjys933dt0lp401360n3hom6o") {
conferencesConference {
name
}
attendeesAttendee {
name
}
}
}

แค่เพียงเท่านี้ระบบก็จะรู้แล้วว่า ชั้นต้องลบยัย Paul ออกจาก GraphQL Conf 2019

สรุปเลยแล้วกัน (Summary)

ขอสรุปแบบสั้นๆ ง่ายๆ ได้ใจความว่า GraphQL เป็นอีกหนึ่งตัวเลือกที่น่าสนใจการทำงานร่วมกับ API เพราะมันช่างง่าย สะดวก และทำให้เราได้ข้อมูลที่เพียงพอต่อการนำไปใช้งาน รวมไปถึงทำให้ข้อมูลอยู่ในรูปแบบที่เราต้องการโดยไม่ต้องไปทำอะไรยุ่งยากซับซ้อนอีกต่อไป

ข้อดีอีกอย่างก็คือการมี API Schema (และบางตัวอาจจะมี Doc Description เพิ่มมาให้อีก) ทำให้สามารถเข้าใจการทำงานและการใช้งานคำสั่งๆ ต่างๆได้ง่ายมากขึ้น

ดังนั้น หากใครที่กำลังปวดหัวอยู่กับการใช้ RESTful API ลองชายตามาเหลียวแล GraphQL สักนิดสักหน่อย คุณอาจจะติดใจหนุบหนับในความ compact but powerful ของน้องคนนี้ก็ได้น้าาา

เดี๋ยวในหัวข้อต่อไปจะพูดถึงการนำ GraphQL ไปใช้งานร่วมกับการเขียน iOS ด้วยเจ้าตัวที่มีชื่อว่า Apollo iOS ถ้าใครสนใจก็อย่าลืมติดตามด้วยนะจ๊ะ บ้ายบายยยยย

พาร์ทสองมาแล้ววว บทความนี้จะเป็นการนำ GrapgQL ไปใช้โดยอาศัย Client ที่มีชื่อว่า Apollo iOS เพื่อเชื่อมข้อมูลแอพพลิเคชั่นเข้ากับ Server จ้า ตามไปดูกันได้เลยจ้าาาา

References

https://graphql.org
https://www.graph.cool/
https://raw.githubusercontent.com/sua8051/AlamofireMapper/master/user1.json
https://www.robinwieruch.de/why-graphql-advantages-disadvantages-alternatives/
https://www.raywenderlich.com/595-getting-started-with-graphql-apollo-on-ios

--

--