ยินดีต้อนรับเข้าสู่ EP ที่ 2 ของ Go Tutorial Series ของเรากันครับ ใน EP ที่แล้ว เราได้ทำการสร้าง API โดยใช้ Gin Framework ขึ้นมากัน โดยเราจะเก็บข้อมูลต่างๆ ของ API ไว้ใน Memory ของ Server ของเรา
ซึ่งใน EP นี้เราจะมาเรียนรู้วิธีการเก็บข้อมูลเหล่านี้ลงใน Database ด้วย Gorm Library กันครับ โดยเราจะทำการเก็บข้อมูลลงใน SQLite Database ที่เป็น Database ประเภทที่น้ำหนักเบา และใช้งานได้ง่าย เพราะมันจะเก็บข้อมูลเป็นไฟล์ปกติในเครื่องเราเลย (ซึ่งจริงๆ แล้ว Gorm สามารถเอาไปใช้กับ SQL Database ได้เหมือนกันครับไม่ว่าจะเป็น MySQL หรือ PostgresSQL ก็สามารถทำได้เช่นกัน)
สำหรับคนที่ยังไม่เคยสร้าง REST API ด้วยภาษา Go หรือ Gin Framework มาก่อน ผมแนะนำให้ลองอ่านบทความ EP. 1 ของเราที่นี่ก่อนได้เลยครับ
เริ่มต้นจากการ Implement แบบง่ายๆ
1. Import Gorm และ SQLite Driver
Import Gorm และ SQLite driver ในไฟล์ main.go
ของเรา
ติดตั้ง dependencies ต่างๆ โดยใช้คำสั่ง
go get go mod tidy
2. ทำการ Initialize Gorm ใน main function
เราเริ่มต้นจากการสร้าง global variable ที่ชื่อdb
เพื่อทำการเก็บ instance จากการสร้าง instance ด้วยคำสั่ง gorm.Open
ประกอบกับการใช้ driver ที่สร้างโดยคำสั่ง sqlite.Open
ซึ่งเราทำการตั้งชื่อไฟล์ของฐานข้อมูลเราว่า test.db
ซึ่งจริงๆ สามารถที่จะตั้งชื่อเป็นอะไรก็ได้ตามต้องการ แค่ให้มีนามสกุลเป็น .db
ก็เพียงพอ
หลังจากนั้นเราใช้คำสั่ง db.AutoMigrate
ในการสร้าง Table ต่างๆ ของเราเข้ามา ซึ่งเราจะต้องทำการใส่ pointer ของ Struct Book
ของเราเข้าไปเพื่อทำให้ Gorm รู้ได้ว่า Table ต่างๆ นั้นต้องสร้าง Table ชื่ออะไร และมีหน้าตาแบบไหนบ้าง อย่างเช่นในกรณีนี้ ชื่อ Table ก็จะเป็น books
ตามชื่อของ Struct Book
ของเรานั่นเอง
3. ปรับ List Handler ให้มาใช้ Gorm
เรามาเริ่มต้นจากการเปลี่ยนใน GET handler ของเรา จากเดิมที่เป็น Logic ในการเอาข้อมูล Slice มาเป็น Response เปลี่ยนเป็นการดึงข้อมูลจาก Database แทนโดยใช้คำสั่ง db.Find(&books)
สังเกตว่าข้อมูลที่เรา query ออกมาจาก database นั้นจะไม่ได้ออกมาจาก return value ของคำสั่ง db.Find
แต่ว่ามันจะอยู่ในตัวแปร books
ที่เราใส่เป็น argument เข้าไปแทน และในส่วนของ return value จะเป็นข้อมูลต่างๆ เกี่ยวกับการ query แทน เช่น error ต่างๆ ที่อาจจะเกิดขึ้นในตอนที่ query ข้อมูลเป็นต้น
ซึ่งเมื่อการ query จาก database นั้น อาจจะมีโอกาสที่เกิด error ขึ้นได้ ดังนั้นเราก็จำเป็นที่จะต้องทำการ handle error เหล่านี้ และคืน response เป็น 500 Internal Server Error
พร้อมกับ error message ไปยัง client ของเรา
4. ปรับ Create Handler ให้มาใช้ Gorm
ต่อมาเรามาทำการเปลี่ยน Logic เดิมใน POST handler เป็นการ insert ข้อมูลของเราเข้าไปใน Database แทนด้วยคำสั่ง db.Create(&book)
ซึ่งสังเกตว่าเราจะทำการส่งเป็น pointer เข้าไป เนื่องจากว่าอาจจะมี field บางอย่างที่ตัว database จะสร้างให้เราแบบอัตโนมัติ เช่น auto-increment id หรือ timestamps ต่างๆ
5. ปรับ Delete Handler ให้มาใช้ Gorm
และสุดท้าย เรามาปรับ DELETE handler ของเราให้มาใช้คำสั่ง db.Delete
ในการลบข้อมูลจาก database แทน ซึ่งเราจะต้องใส่ argument เข้าไป 2 อัน นั่นก็คือ Pointer ของ Struct Book
ของเรา และ ID ของ row ที่เราต้องการจะลบเข้าไป
สาเหตุที่เราต้องใส่ Pointer ของ Struct Book
เข้าไปนั้นก็เพราะว่า Gorm จะได้รู้ว่าเรากำลังต้องการจะ Delete ข้อมูลใน Table ไหน ซึ่งในทีนี้ก็คือ Table books
ของเรานั่นเอง
6. ตอนนี้ไฟล์ main.go จะมีหน้าตาเป็นแบบนี้
วิธีการทดสอบ API
ตอนนี้เราก็สามารถทดสอบ API ของเราเหมือนกับที่เราเคยทำก่อนหน้านี้ด้วย REST API client extension บน VSCode สามารถดูวิธีการเพิ่มเติมได้ที่ Tutorial EP.1
ปัญหาของการใช้ Global DB Variables
ตอนนี้เรากำลังใน global variable ชื่อ db
ภายในโค้ดของเรา ซึ่งมันสามารถทำให้เกิดปัญหาในอนาคตได้ เพราะมีโอกาสที่บาง function ที่สามารถเข้ามาเปลี่ยนแปลงตัวแปร db
ของเราได้ ซึ่งเราจะมาดูวิธีการแก้ไขปัญหานี้กัน
ใช้วิธีการ Dependency Injection
หลักการ Dependency Injection เราอาจจะคุ้นเคยกับภาษาในรูปแบบที่เป็น OOP อย่าง Java และมันอาจจะฟังดูยากและซับซ้อน แต่ในความเป็นจริงแล้ว มันเป็นเพียงแค่การเอา dependencies ต่างๆ ที่เราต้องการใช้ ตัวอย่างเช่น ตัวแปร db
ของเรานั้น ให้คนอื่นเป็นคนสร้าง และส่งต่อให้กับ class ที่จะใช้ db
ผ่าน constructor ได้
ซึ่งในภาษา Go จะไม่มี class เหมือนกับภาษาอื่นๆ ที่เป็น OOP แต่ว่าเรามีซึ่งที่ใกล้เคียงกันนั่นก็คือ Struct นั่นเอง ซึ่งเราจะทำการสร้าง Handler
struct ขึ้นมาเพื่อทำการเก็บตัวแปร dependencies ต่างๆ ของเรา ซึ่งในที่นี้ก็คือ *gorm.DB
นั่นเอง และเราก็ทำการเปลี่ยน handler function ของเราเป็น method แทน และสุดท้ายก็สร้าง method สำหรับการสร้าง Handler struct นี้ก็เป็นอันเสร็จ
พูดแล้วก็ไม่ค่อยเห็นภาพ เรามาดูตัวอย่างการเขียนโค้ดกันเลยดีกว่า!
1. สร้าง Handler Struct และฟังก์ชัน newHandler
เราจะทำการสร้าง struct ที่ชื่อว่า Handler
ขึ้นมาพร้อมกับ function newHandler
ของเราที่ทำหน้าที่คล้ายๆ กับ constructor ในภาษา OOP อื่นๆ โดยการจะสร้าง Handler
ด้วยnewHandler
นั้นจะต้องใช้ dependency อย่าง *gorm.DB
2. Make handler function to be method and use the db from Handler
ต่อมาเราจะทำการเปลี่ยน Handler Function ต่างๆ ก่อนหน้านี่ให้กลายเป็น Method ของ Struct Handler
ซึ่งเราสามารถทำได้อย่างง่ายดายโดยการใส่ Receiver (h *Handler)
เข้าไปในแต่ละ method แค่นี้เราก็ได้ method มาใช้งานแล้ว
และเราก็เปลี่ยนจากการที่เราใช้ Global Variable db
มาเป็นใช้ Field h.db
ใน Struct Handler
แทน
3. เปลี่ยน main function ให้มาใช้ Handler struct แทน
สุดท้าย เราก็จะมาสร้าง db instance ใน main function และทำการส่ง instance นี้ไปใน newHandler
function เพื่อทำการสร้าง Handler ขึ้นมา และเราก็สามารถใช้ method ของ Handler มาแทน function ของเราก่อนหน้านี้ได้เลยตามโค้ดด้านบนนี้
สุดท้ายหน้าตาของ Code เราจะเป็นแบบนี้
เสร็จแล้ว! ที่นี้เราก็สามารถสร้าง REST API โดยเก็บข้อมูลลง Database ด้วย Gorm ได้เรียบร้อยแล้ว
ตอนต่อไปใน Series นี้
ใน EP ถัดไป เราจะมาลองดูวิธีการทำ Authorization กับ API ของเราโดยการใช้ JSON Web Token (JWT) และ Middleware ของ Gin