GIN 101-S: คุยกับ MongoDB และ Redis บน Golang
หลังจากที่เราได้สร้าง web service กันไปใน part ที่แล้ว สิ่งสำคัญอีกอย่างหนึ่งที่ web service เกือบทั้งหมดจะต้องมีนั่นก็คือ database นั่นเอง ซึ่งในบทความนี้ ผมจะพูดถึงการใช้ Golang คุยกับ database 2 ชนิดได้แก่ MongoDB และ Redis ครับ แต่ก่อนอื่น เรามาทำความรู้จักกับ database ทั้งสองชนิดนี้กันครับ
MongoDB
MongoDB คือ non-relational database ที่ใช้ระบบการเก็บข้อมูลแบบ document-based ซึ่งจะเก็บข้อมูลในรูปแบบคล้าย JSON document โดยทางผู้สร้างเชื่อว่าการเก็บข้อมูลในลักษณะนี้ เป็นการเก็บข้อมูลได้หลากหลายและเป็นธรรมชาติมากที่สุด (เนื่องจากระบบเก่า หรือ SQL จะเก็บข้อมูลโดยแบ่งเป็น row และ column) ซึ่งทางผู้สร้างก็ทำการ claim ว่า ไม่มี database ไหนที่จะสามารถทำให้งานของคุณ productive มากไปกว่านี้ได้อีกแล้ว
Redis
Redis คือ open source database ที่เก็บข้อมูลแบบ in-memory data structure store โดยเก็บข้อมูลในรูปแบบ key-value เนื่องจากเป็นการเก็บข้อมูลใน memory (หรือก็คือ RAM) ทำให้เวลาที่ใช้ในการประมวลผล (query) น้อยมาก แต่ด้วยการเก็บข้อมูลที่มีเพียงแค่ key กับ value ประกอบกับการเก็บข้อมูลไว้บน RAM จึงทำให้ redis ไม่เหมาะกับการเก็บข้อมูลระยะยาวและมีจำนวนมากเหมือน MongoDB
โดยส่วนมาก redis จะถูกใช้เพื่อทำ caching หรือเป็น temporary database เพื่อรองรับ concurrent connection ในปริมาณมาก และ เพิ่มความรวดเร็วในการประมวลผลของระบบเท่านั้น (แก้ปัญหาคอขวดใน database หลักเพราะ query ไม่ทัน)
Prerequisite
มาเริ่มกันเลยดีกว่า! (บทความนี้ไม่สอนลง Mongo กับ Redis นะ) ในบทความนี้ผมจะใช้ docker ในการ run mongo กับ redis service (เพราะเค้ามี image ที่ build มาเสร็จเรียบร้อยแล้ว) ถ้าหากใครที่ยังไม่รู้วิธีใช้ docker แนะนำว่าให้อ่านบทความนี้ก่อนครับ (หรือถ้ามี mongo กับ redis อยู่แล้วก็ใช้อันนั้นได้นะ)
ถ้ามี docker แล้ว เรามา run docker image กันเลย!
$ docker run -d -p 27017:27017 mongo$ docker run -d -p 6379:6379 redis
คำสั่ง docker run
จะ pull image ให้อัตโนมัติหากเราไม่มี image นั้นอยู่ หลังจาก run แล้ว เรามาลองเช็คดูว่า image ของเรา run แล้วจริงหรือเปล่าด้วยคำสั่ง
$ docker ps
หากได้ผลลัพธ์ตามนี้แสดงว่า image ของเรา run ได้อย่างถูกต้อง
Installation
เราจะใช้ 2 repo หลักๆ ในการเข้าไปเป็นมะริ่งกิ่งก่อง สะระน๊องก่องแก่งกับ database นั่นก็คือ mgo
และ go-redis
ครับ (ในส่วนของ redis ใน repo เค้าให้ลง v7 ด้วย go mod แต่จากประสบการณ์ของผม ใช้ตัวธรรมดาจะง่ายต่อการลงมากกว่าครับ)
$ go get -u github.com/globalsign/mgo$ go get -u github.com/go-redis/redis
import เพื่อใช้งาน
$ import "github.com/globalsign/mgo"$ import "github.com/go-redis/redis"
Connect with MongoDB
สำหรับ mongo เราจะใช้ library อีก 1 ตัวเพื่อช่วยในการ query นั่นคือ bson ครับ
$ go get -u github.com/globalsign/mgo/bson$ import "github.com/globalsign/mgo/bson"
ทีนี้เราก็พร้อมคุยกับ database แล้ว! อันดับแรกเรามาลองเชื่อมต่อกับ database ดูกันครับ เริ่มจาก mongo ก่อน
จากในตัวอย่าง เราจะทำการเชื่อมต่อ mongo ด้วยคำสั่ง mgo.Dial
โดยใส่ argument เป็น localhost:27017
โดย function นี้จะ return session
กลับมา ซึ่งเราจะใช้ตัวนี้ในการทำงานทั้งหมด หลังจากได้ session แล้ว เราก็ทำการเลือก database และ collection ที่เราจะใช้ ด้วยคำสั่ง
mongoConnection := session.DB("dummy").C("testCollection")
โดย DB
จะเป็นการเลือก database และ C
จะเป็นการเลือก collection การเลือก database และ collection นั้น ไม่จำเป็นว่าจะต้องมี database และ collection อยู่ก่อนแล้ว เราสามารถตั้งชื่อใหม่ให้มันเลยได้
เนื่องจาก mongo เก็บข้อมูลในรูปแบบ BSON เราจึงต้องเตรียม data โดยตั้งชื่อให้มันใน BSON format ด้วยครับ
type test struct {
Name string `bson:"name"`
Number int `bson:"number"`
}
หลังจากเลือกแล้ว เราก็มาลอง insert document ด้วยคำสั่ง
mongoConnection.Insert(t)
ซึ่งคำสั่งนี้ก็จะทำการยัดข้อมูลที่เราเตรียมไว้เข้าไปใน collection ที่เลือกไว้ทันที ถ้าหากเราเลือก database และ collection ที่ยังไม่เคยมีมาก่อน มันจะทำการสร้าง database และ collection ให้โดยอัตโนมัติครับ ซึ่งหากทำทุกอย่างถูกต้อง data ที่เข้าไปอยู่ใน mongo จะมีหน้าตาแบบนี้ครับ (อันนี้เข้าไปดูด้วย MongoDB Compass)
หลังจาก insert ได้แล้ว เรามาลอง query กันดูเลยครับ
ในการ query เราจะใช้คำสั่ง Find
ตามด้วย query ในรูปแบบ bson ครับ (ก็คือเขียนเหมือน json แต่เอา bson.M
มาครอบข้างหน้า) จากนั้นเราใช้ .One
เพื่อเป็นการบอกว่า เราต้องการ query แค่ document เดียว แล้วทำการ assign เข้าไปในตัวแปร result
โดยจะได้ผลลัพธ์หน้าตาแบบนี้ (ผมใช้ spew
ในการ print เพราะต้องการดูรายละเอียดของ object ครับ)
เรายังสามารถเลือกที่จะ assign ข้อมูลที่ query มาใส่ตัวแปรที่กำหนด structure ไว้แล้วได้ด้วย (ยังจำ type test ข้างบนได้ใช่มั้ย ;w;)
var formatResult test
err = mongoConnection.Find(bson.M{"name":"test"}).One(&formatResult)
spew.Dump(formatResult)
ซึ่ง function นี้ก็จะทำการ map ข้อมูลที่ได้มากับ field ที่มีอยู่ใน structure ให้ครับ
ถ้าเราพยายาม query สิ่งที่ไม่มีอยู่ใน collection ตัว function ก็จะ return กลับมาว่า not found
อยู่ใน error ครับ
อย่าลืมปิด session
หลังใช้เสร็จด้วยนะครับ
session.Close()
Connect with Redis
สำหรับ redis ไม่ต้องลง library เพิ่มครับ เรามาลองเชื่อมต่อกับ redis กันเลย
ถ้าเชื่อมต่อสำเร็จ สิ่งที่โปรแกรม print ออกมาจะเป็นคำว่า PONG
และ <nil>
ครับ โดยเราจะเชื่อมต่อไปที่ database 0 ซึ่งเป็น default database (database ของ redis จะเป็น running number เริ่มที่ 0 ครับ)
เนื่องจาก redis เก็บข้อมูลเป็น key และ value การเตรียมข้อมูลเพื่อใส่เข้าไปใน redis จะค่อนข้างแตกต่างกับ mongo พอสมควรครับ
โดยคำสั่งที่ใช้ยัดข้อมูลเข้าไปใน redis จะใช้ function Set()
ครับ ซึ่ง function นี้รับ 3 argument นั่นก็คือ key
, value
, และ expiration
ซึ่งถ้าตั้งเป็น 0 หมายถึงไม่มีวันหมดอายุนั่นเองครับ (แต่จริงๆ แล้วไม่ดีนะ เพราะถ้าเราใส่ data เข้าไปเรื่อยๆ โดยที่ไม่มีตัวไหนหมดอายุไปเลย สุดท้ายแล้ว memory มันก็จะเต็ม)
ในตัวอย่างนี้ผม set ข้อมูลเข้าไป 2 ชุดนั่นก็คือ
- key
name
มี valuetest
- key
number
มี value200
ส่วนการ query ข้อมูล เราจะทำโดยใช้ function Get()
แล้วตามด้วย Result()
ถ้าการ query สำเร็จ result ที่ได้จะเป็นค่า value ของ key นั้นๆ แต่หากเรา query key ที่ไม่มีอยู่ redis จะ return ค่า redis: nil
กลับมาครับ
เสร็จแล้วก็อย่าลืมปิด connection เหมือนเดิมครับ
err := redisClient.Close()
Conclusion
มาถึงตรงนี้ คิดว่าทุกคนน่าจะพอเข้าใจ concept ในการคุยกับ database บน Golang พอสมควรนะครับ (รึเปล่าหว่า) จริงๆ แล้วบทความนี้ไม่ได้มีการพูดถึง Gin แต่เป็นการเกริ่นเรื่องก่อนที่จะเอาไปใช้ด้วยกันใน part ถัดๆไป (จริงๆ คือตอนแรกกะจะรวมอยู่ในบทความเดียวแต่ไม่อยากให้ part นึงมันยาวเกินไป เลยแยกอันนี้ออกมาเป็นเหมือน side story ครับ) หวังว่าบทความนี้จะเป็นประโยชน์ได้บ้าง และทุกคนจะไม่ประท้วงผมตามข้อบังคับที่ ๖๙ นะครับ 😂