GIN 101: สร้าง Web Service บน Golang
What is Gin?
Gin เป็น web framework ที่เขียนด้วยภาษา golang ที่ถูกพัฒนาต่อมาจาก Martini ที่หยุดพัฒนาไปแล้ว โดย Gin จะใช้ customized httprouter ทำให้มีประสิทธิภาพด้านความเร็วที่สูงมาก ถ้าคุณกำลังมองหา framework ที่มี performance กับ productivity ที่ดี Gin เป็นหนึ่งในตัวเลือกที่น่าสนใจเลยทีเดียว
Installation
ก่อนติดตั้ง Gin จะต้อง install Go และ Go workspace ก่อนนะครับ สำหรับคนที่ยังไม่ได้ติดตั้ง ลองเข้าไปอ่านบทความนี้ดูก่อนนะครับ สำหรับคนที่มีแล้ว ลองเช็คดูว่า Go version ใหม่กว่า 1.11 หรือเปล่า ถ้าเก่ากว่าต้องอัพเดท version ด้วยนะครับ ไม่อย่างนั้นจะใช้ไม่ได้
$ go get -u github.com/gin-gonic/gin
ใช้งานโดย import เข้าไปใน go file
import "github.com/gin-gonic/gin"
Quick Start
เมื่อ install เสร็จแล้ว เรามาลองใช้กันเลย!
ใน code ตัวอย่างนี้จะเป็นการสร้าง path /ping
ซึ่งเป็น method GET โดยจะ response กลับมาเป็น json ที่มีข้อความ {“message”: “pong”}
หากเรียกสำเร็จ
ทีนี้เรามาลอง run กันครับ
$ go run example.go
หลังจาก program start เสร็จแล้ว ถ้าเข้าไปที่ localhost:8080/ping
ผ่าน web browser ก็จะเจอกับ result หน้าตาแบบนี้
การใช้งาน API
ใน repository ของ Gin มีตัวอย่างที่พร้อมให้ลองใช้งานอยู่ที่ Gin examples repository ลองเข้าไปดูได้ครับ
Gin สามารถตั้ง method ของแต่ละ route ได้ตามที่เราต้องการใช้เลยครับ
เราสามารถเลือก port ที่ต้องการรัน server ได้ด้วยการใส่เลข port เข้าไปเป็น parameter ของ router.Run()
Handler Function & Query
Gin จะใช้ handler function ในการทำ operation ต่างๆ โดยทั่วไปจะใช้ *gin.Context
เป็น Context ในการรับส่งค่าต่างๆ
จากในตัวอย่างผมดึง handler function ออกมาเป็น function แยกเพื่อทำให้การเขียน code สะดวกมากขึ้น (เพราะถ้าเขียนทุกอย่างใน main สุดท้ายมันจะรกมากๆ)
Gin สามารถรองรับการ query ข้อมูลจาก request ได้ค่อนข้างหลากหลาย ในตัวอย่างนี้ผมได้เลือก query ที่ผมใช้บ่อยๆ มาใส่ไว้ครับ โดยผมจะลองทดสอบ API โดยใช้ Insomnia ในการยิง request
สำหรับ function exampleFunc
จะเห็นได้ว่า เราเรียกใช้การดึงข้อมูลสองรูปแบบคือ c.Query
และ c.PostForm
c.Query
จะเป็นการดึงข้อมูลจาก parameter ของ request โดยใช้ชื่อ key (ในที่นี้ก็คือส่วนของid
ซึ่งอยู่ต่อจาก urlc.PostForm
จะเป็นการดึงข้อมูลจาก Multipart หรือ Urlencoded Form ด้วย key เช่นกัน
สำหรับ function exampleJSON
จะเป็นการ bind ข้อมูลมาเก็บไว้ใน structure โดยใช้คำสั่ง c.BindJSON
ซึ่งคำสั่งนี้จะทำงานคล้ายๆ กับการ Unmarshal
เพียงแต่ใช้ input จาก request แทนการใช้ตัวแปร แต่การใช้ c.BindJSON
นั้นจะสามารถทำได้ครั้งเดียว นั่นหมายความว่า หาก bind ไปแล้วข้อมูลส่วนนั้นใน request จะหายไปเลย ไม่สามารถ bind ซ้ำได้อีก
Middleware
Middleware จะเป็นส่วนที่ค่อนข้างมีความสำคัญมากในการสร้าง web service ด้วย Gin ซึ่งจะมีบทบาทหลักในการทำ authentication และ custom logging หรือถ้าหากเราต้องการให้มี function กลางที่ต้องทำก่อนใช้ API โดยจะมีรูปแบบการใช้งานดังนี้
result:
ora
[GIN] 2019/12/24 - 18:27:34 |200|0s|::1|POST /user/test?id=1234
ora
muda
[GIN] 2019/12/24 - 18:27:42 |200|0s|::1|POST /admin/test
Middleware function จะ return gin.HandlerFunc
หรือก็คือ function รูปแบบเดียวกับที่เราทำไปก่อนหน้านี้นั่นเอง คำสั่ง r.Use
จะเป็นการประกาศว่าทุกๆ route ที่อยู่ใต้บรรทัดนี้จะต้องผ่านการ execute จากบรรทัดนี้ก่อนเท่านั้น
Group
การแบ่งกลุ่มพูดง่ายๆ ก็คือการแยก parent path นั่นเองครับ การใช้ group จะมีประโยชน์คือใช้ในการแบ่ง API ออกเป็นกลุ่มๆ ทำให้ง่ายต่อการใช้งาน และยังสามารถสร้าง middleware มาครอบเฉพาะกลุ่มได้ด้วย ตัวอย่างเช่น
ถ้าเรายิงไปที่ path /user/test
ระบบจะผ่านแค่ function exampleMiddleware
ก่อนที่จะเริ่มทำในส่วนที่เป็น API แต่ถ้ายิงไปที่ /admin/test
ระบบจะผ่าน exampleMiddleware
และ authenticationMiddleware
ก่อนจึงจะทำในส่วน API
gin.Context
จริงๆแล้วเจ้าตัว c
หรือ *gin.Context
นั้นมีอะไรให้เราเลือกใช้ได้มากกว่าการ query หรือ ทำ JSON response โดยเฉพาะใน API ที่ต้องการ middleware ยกตัวอย่างเช่น บางทีเราอาจจะต้อง bindJSON ใน middleware แต่ก็ต้องการใช้ json นั้นใน API ด้วย หรือ เราอาจจะอยากทำ custom log ที่ต้องการเก็บข้อมูลทั้งก่อนและหลัง API process ตัว context ของ Gin รองรับ procedure พวกนี้ทั้งหมด โดยผมจะยกตัวอย่างตัวที่ผมได้มีโอกาสใช้บ่อยๆ ให้ดูครับ (สามารถดู context command ทั้งหมดได้ ที่นี่ ครับ)
ในตัวอย่างจะมี command ตามนี้ครับ
c.Set()
จะเป็นการ set key และ value เข้าไปใน context เพื่อทำให้สามารถนำค่านั้นไปใช้ที่อื่นที่ c ไปถึงได้ (เช่น middleware กับ API function)c.Get()
เป็นการ get ข้อมูลที่ได้มีการ set ไว้ก่อนแล้วด้วย key โดยที่ function จะ return value ที่ set ไว้พร้อมกับ boolean 1 ตัวที่บอกว่า key นั้น exist หรือเปล่าc.Next()
คือการสั่งให้ระบบข้ามไปทำใน function ต่อไป (อาจจะเป็น middleware อีกตัวหรือส่วน API เลยก็ได้) แล้วจึงกลับมาทำส่วน middleware ต่อหลังจาก API process เสร็จสิ้นc.Abort()
คือสั่งให้ process จบ ณ ตรงนั้น
result:
testFunc
[GIN] 2019/12/24 - 17:40:57 | 200 | 889.7906ms | ::1 | POST /test
จริงๆ แล้วยังมี c.Request
ที่สามารถดึงข้อมูลของ request (เช่น header, host, method) ได้อีกด้วย แต่เนื้อหาส่วนนั้นค่อนข้างกว้างจึงไม่ได้ใส่มาในบทความนี้ครับ
Conclusion
ก็จบไปแล้วกับ Gin 101 ครับ ผมรู้สึกว่าสำหรับผมแล้ว ผมใช้เวลากับการศึกษา middleware ค่อนข้างนานกว่าจะใช้ได้คล่องจึงอยากมาแชร์ส่วนนี้ก่อนโดยที่ยังไม่ได้พูดถึงเกี่ยวกับการคุยกับ database (จริงๆ ยังเลือกไม่ถูกว่าจะพูดถึงตัวไหนดี) เพราะส่วนตัวผมใช้คู่กับ httpRequest ด้วยการจับเวลาใน middleware เลยช่วยได้เยอะครับ ส่วนตัวจากที่ใช้ Gin มารู้สึกว่ามันอาจจะไม่ได้ใช้ง่ายเหมือน Node ที่เป็น javascript แต่เรื่องประสิทธิภาพและความเร็วถือว่าเหนือกว่าค่อนข้างเยอะเลยครับยังไงก็ขอฝาก Gin ไว้เป็นตัวเลือกสำหรับคนที่ต้องการ web service ที่มี performance กับ productivity ที่ดีด้วยนะครับ