Cloud Firestore — Collection Group Queries สิ่งที่จะเข้ามาเปลี่ยนการออกแบบฐานข้อมูลไปตลอดกาล

Thanongkiat Tamtai
Firebase Thailand
Published in
4 min readJul 5, 2019
https://3.bp.blogspot.com/-j9G1FSNk8gw/XQqlQGI_I3I/AAAAAAAADoI/ldmeyycIbmMbcH7-rUHX60rfNIMkDdc7gCLcBGAs/s1600/1.png

เมื่องาน Google I/O 2019 ที่ผ่านมา Firebase Cloud Firestore ก็มี Feature ออกมาใหม่ชื่อว่า Collection Group Queries ซึ่งย้อนไปตั้งแต่การเปิดตัว Cloud Firestore เมื่อปี 2017 ก็จะมี Feature เนี้ยแหละที่ผมรู้สึกว่ามันเป็น Major upgrade ที่ดูแล้วรู้สึกว้าวที่สุดที่เห็นผ่านมา ใครที่ยังไม่รู้ว่า Collection Group Queries มันคืออะไรและจะมีประโยชน์อย่างไรต่อคนที่ใช้ Cloud Firestore อยู่ในปัจจุบันหรือจะเป็นผู้ใช้ใหม่ก็ตาม เดี๋ยวผมจะพาไปรู้จักมันด้วยกัน

Collection Group Queries คืออะไร ?

Collection Group Queries คือรูปแบบการสอบถามข้อมูลรูปแบบใหม่ที่ทำให้เราสามารถ Query Collection ซึ่งเป็น Collection หรือ Subcollection ที่อยู่ซ้อนกันลึกขนาดไหนก็ได้หรืออยู่ข้าม Collection กันก็ได้ เพียงเราระบุชื่อของ Collection ที่เราต้องการลงไปก็จะสามารถเข้าถึง Collection นั้นๆ โดยไม่ต้องสลับ Path ระหว่างๆ Collection / Document / Collection / Document ไปเรื่อยๆ แบบปกติได้

ซึ่งกฏของการที่คุณจะนำ Collection Group Queries มาใช้ได้ คุณจะต้องรู้กฏอยู่ 3 ข้อด้วยกัน

  • Collection ทั้งหมด ( ซึ่งผมจะขอเรียกว่า Collection Group แล้วกันนะ ) ต้องเป็นชื่อเดียวกัน เหมือนกันเป๊ะๆ
  • คุณต้องทำการกำหนดการทำ Indexing ของ Collection Group เพิ่มและเพิ่มเติมขึ้นไปอีกหากคุณต้องการ Combine การ Query แบบอื่นๆ เช่น Where, Order ผสมลงไปด้วย ( ตัวอย่างจะอยู่ข้างล่าง )
  • ทุก ๆ ที่ ที่มี Collection Group ชื่อที่กำหนดอยู่จะต้องทำการกำหนด Security rules การ Read เป็น True

วิธีนำ Collection Group Queries มาใช้งาน

วิธีการที่คุณจะนำ Collection Group Queries เข้ามาใช้งานก็ง่ายมาก เพียงแค่เรา upgrade SDK เป็นเวอร์ชั่นล่าสุดและทำการเรียกคำสั่ง

var firebase = require('firebase');firebase.firestore().collectionGroup('ชื่อของCollection')

หากมีเงื่อนไขเพิ่มเติมก็สามารถระบุเข้าไปเพิ่มได้ เช่น

firebase.firestore().collectionGroup('ชื่อของCollection').where('ชื่อfield', 'เครื่องหมายเปรียบเทียบทางคณิตศาสตร์', 'ค่าที่ระบุ').orderBy('ชื่อfield', 'asc หรือ desc')

ข้างบนเป็นตัวอย่างจาก JavaScript SDK หากต้องการดูคำสั่ง Platform อื่น คลิกที่นี้

หลังจากนั้นเราจะไปกำหนด Indexing กันใน Firebase console

ไปที่ Tab Database ทางซ้ายมือ -> Cloud Firestore -> Indexing (ดัชนี)

ปกติแล้วการทำ Indexing ชนิด Single Field (ช่องเดี่ยว) จะมีการทำ Auto Indexing โดยระบบเบื้องหลังของ Cloud Firestore อยู่แล้ว แต่เราต้องการที่จะใช้ Collection Group Queries เราต้องไปทำการ Custom Single Field Indexing ขึ้นมาโดยการเลือกเมนู ช่องเดี่ยว (Single Field) และกดปุ่ม เพิ่มการยกเว้น (Add exemption)

จะปรากฏ Form ให้เราทำการกรอกดังรูป

ให้เราทำการใส่ ชื่อ Collection , ชื่อ Field , Scope ของการค้นหาก็ระบุไปเป็น Collection Group จากนั้นกดปุ่ม Next

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

อีกกรณีคือเราต้องการที่จะทำ Indexing แบบมีการผสมเงื่อนไขได้หลายๆ Field

เลือก ดัชนีผสม และกดปุ่ม เพิ่มดัชนี

ซึ่งจะมี Form ออกมาให้เรากรอกข้อมูลลงไป เช่น ชื่อ Collection , ชื่อ Field ( อย่างน้อย 2 ช่อง )และรูปแบบการเรียงลำดับ, Scope ของการค้นหาก็ระบุไปเป็น Collection Group

ตรงจุดนี้มีข้อควรระวังนิดนึงคือ การที่เราระบุชื่อ Field และรูปแบบการเรียงลำดับไปนั้นให้ใส่ลงไปเฉพาะที่เราจำเป็นต้องใช้จริงๆ ห้ามใส่อะไรลงไปเผื่อเด็ดขาด หากคุณใส่อะไรเข้าไปไม่ตรงกับที่ต้องการจะใช้งานจริง มันจะถือว่าเป็นคนละหัวข้อของการทำ Indexing เช่น ดังรูปจะเห็นได้ว่า เริ่มต้นมาด้วย OwnerUserID,ASC ตามด้วย ReactionUserID,ASC หากเราเพิ่มอย่างอื่นเผื่อเข้าไปเช่น ReactionType,DESC เข้าไปด้วย กรณีนี้จะถือว่าเป็นคนละกรณีกับกรณีด้านบน ถ้าเกิดเราต้องการให้สามารถ Query ได้ทั้งสองกรณี เราต้องมาทำ Indexing ไว้ทั้งหมดสองชุด และตอนที่เขียน Code ส่งคำสั่ง Query เข้ามาก็ต้องตรงกับการทำ Indexing ไว้ชุดใดชุดหนึ่งที่เราทำไว้ เช่น

กรณีแรก (1.OwnerUserID,ASC 2.ReactionUserID,ASC)firebase.firestore().collectionGroup('Reaction').where('OwnerUserID', '==', 'User1').orderBy('OwnerUserID', 'asc').where('ReactionUserID', '==', 'User2').orderBy('ReactionUserID', 'asc')

สุดท้ายที่เราต้องทำก็คือไปทำการกำหนด Security rules

ไปที่ Tab Database ทางซ้ายมือ -> Cloud Firestore -> Rule (กฏ)

ขั้นแรกกำหนดเป็น rule version 2 และทำการกำหนด ให้ทุกๆ Path ที่ลงท้ายตามด้วยชื่อ Collection ที่เราระบุ ( ในตัวอย่างคือ Reactions ) อนุญาติให้อ่านได้ ( Allow read )

หากเราทำครบทุกขั้นตอน ณ ตอนนี้ Cloud Firestore ของเราก็พร้อมที่จะใช้ Collection Group Queries แล้วจ้า

การมาของ Collection Group Queries จะมีผลกระทบไปยังส่วนไหนบ้าง

มาเข้าสู่ช่วงมีสาระ(กว่าข้างบน)กันบ้าง ซึ่งก่อนที่เราจะรู้ว่ามันมีผลกระทบไปยังส่วนไหนบ้าง เรามาลองทบทวนกันก่อนดีกว่าว่าการออกแบบโครงสร้างบน Cloud Firestore มันมีกี่รูปแบบบ้าง

  • Nested data
  • Subcollections
  • Root-level collections

แน่นอนว่าที่จะมีผลกระทบหลักๆ จะต้องเป็น Subcollections และ Root-level collections แน่ๆ เนื่องก็มาจากก่อนหน้านี้ใครที่วางโครงสร้างของข้อมูลเป็นแบบ Subcollections จะต้องเจอปัญหาที่ว่า เราจะ Query ข้อมูลข้าม Subcollection ไม่ได้

ยกตัวอย่างเช่น

สมมุติผมออกแบบโครงสร้างของ Facebook ในส่วนของ Post ซึ่งข้างใน Subcollection ก็จะประกอบไปด้วย Subcollection Reactions และ Comments ซึ่งก็จะมี Reactions ของ Comments อีกชั้นนึง

+Posts
>PostDocumentKey
{
Field ต่างๆ
}
+Reactions
>ReactionDocumentKey
{
Field ต่างๆ
}
+Comments
>CommentDocumentKey
{
Field ต่างๆ
}
+Reactions
>ReactionDocumentKey
{
Field ต่างๆ
}

จากตัวอย่างข้างบนหากผมต้องการที่จะอยากได้ข้อมูล “Post ทั้งหมดที่มี Comment Text ประกอบด้วยคำว่า อิอิ” แบบนี้เมื่อก่อนเราจะไม่สามารถทำได้เพราะเราจะสามารถทำได้เพียง Query ของต่างๆจาก Collection เดียวกันเท่านั้นและไม่สามารถเข้าไป Query ลึกลงไปถึงระดับชั้น Subcollection ได้

ทีนี้มันก็จะมีแนวคิดอีกรูปแบบนึงเกิดขึ้นมาว่า งั้นเราก็แยกข้อมูลที่มันมีความสัมพันธ์กันทั้งหมดเนี้ยออกมาเป็นแบบ Root-level collections หมดเลยสิ

+Posts
>PostDocumentKey
{
PostText
}
+Comments
>CommentDocumentKey
{
CommentText
PostDocumentKey
}
+Reactions
>ReactionDocumentKey
{
ReactionLevel ('Post' / 'Comment')
ReactionType ('Like', 'Love', 'Wow', 'Haha', 'Angry')
PostDocumentKey
CommentDocumentKey
}

แบบนี้เป็นต้น ถ้าหากอยากจะได้ข้อมูลของ Comment อย่างไรก็เพียงแค่ Query ผ่านการ Where ด้วย PostDocumentKey ก็จะเป็นการเชื่อมต่อความสัมพันธ์ทั้งสองได้แล้ว แต่การที่เราออกแบบโครงสร้างแบบนี้ ทุกๆอย่างส่วนมากจะมากองกันใน Document ก็จะทำให้เกิดปัญหาในอนาคตได้ เช่น

  • Document Size ใหญ่จนเกินไป Maximum size อยู่ที่ 1 MiB (1,048,576 bytes)
  • เราไม่สามารถ Filter เอาแต่ Field ที่เราต้องการได้อย่างเดียว แต่จะได้ Field ที่มันอาจจะไม่ได้จำเป็นต่อการอ่านข้อมูลในครั้งนั้นออกมาด้วย
  • ได้ข้อมูลขยะมาโดยไม่จำเป็นทำให้เกิดการเปลือง Bandwidth
  • จัดการกับการเขียน Security rules ค่อยข้างยาก

ทั้งหมดที่ยกตัวอย่างมา Collection Group Queries ก็จะมาแก้ไขปัญหาทั้งหมดนี้

แนวทางจะเป็นยังไงหลังจากการมี Collection Group Queries

โดยแนวคิดการออกแบบหลังจากการมี Collection Group Queries มาก็จะเน้นการออกแบบความสัมพันธ์ของข้อมูลในรูปแบบของ Subcollections เป็นส่วนใหญ่ และใช้ Root-level collections เท่าที่จำเป็นหรือในส่วนของข้อมูลที่ไม่มีความสัมพันธ์กัน

การออกแบบในรูปแบบของ Subcollection ที่จะใช้ Collection Group Queries เป็นหลักก็คือแนะนำให้ระบุ DocumentKey จากลำดับชั้นที่เป็น Parent เช่น ใน Collection Comment ก็ควรมี PostDocumentKey อยู่ในแต่ละ Comment Document ด้วยหรือหาก User คนไหนเป็นคนกระทำต่างๆ ก็ควรมี UserKey (UID) เก็บไว้ด้วยเช่น ใน Collection Reaction ควรเก็บ ReactionUserID ว่าใครมาทำการกดปุ่ม Reaction นี้ และใครที่เป็นเจ้าของ Post หรือ Comment นั้นๆ ด้วย OwnerUserID เป็นต้น

หากเราต้องการข้อมูลว่า “User คนนี้ไปทำ Reaction ต่างๆที่ใดบ้าง” เราก็เพียงแค่

firebase.firestore().collectionGroup('Reaction').where('ReactionUserID', '==', 'SomeUID')

หรือหากเราต้องการข้อมูลว่า “User คนนี้ไปทำการกด Reaction แบบ Love กับคนนี้ที่ใดบ้าง”

var GetLove = firebase.firestore().collectionGroup('Reaction').where('ReactionUserID', '==', 'SomeUID').where('OwnerUserID', '==', 'SomeUID').where('ReactionType', '==', 'Love')

ทีนี้ก้อน Object ที่รวบรวมมาให้เราก็จะเป็นทุกๆ Object ของ Reaction Document แล้ว หากเราอยากจะได้ Object ข้อมูลของ Post หรือ Comment นั้นด้วยก็เพียงแค่ เรียก Property .ref.parent แล้วนำไป Query ต่ออีกทอดด้วยวิธีการอื่น ก็สามารถทำได้

ข้อจำกัดของ Collection Group Queries

เนื่องจากการที่เราจะใช้ Collection Group Queries มันจะต้องมีการทำ Indexing ในหลายๆเงื่อนไข เพราะ Collection ชื่อเดียวกันพอไปอยู่ต่างที่ ต่างลำดับชั้นของข้อมูล อาจจะทำให้เรามีเงื่อนไขของการทำ Indexing แตกต่างกัน โดยตามที่ Document ของ Cloud Firestore นั้นระบุไว้ก็จะมีการจำกัดไว้ที่สูงสุด 200 Indexing ลองเข้าไปอ่านดูได้ครับ คลิกที่นี้ ฉะนั้นก็ทำ Indexing ตามที่ใช้งานจริงๆ ไว้จะดีกว่านะ ฮ่าๆ

ฝากก่อนไป

สุดท้ายนี้ฝากกด Follow เพื่อรับการแจ้งเตือนใหม่อย่างรวดเร็วและกดปรบมือเป็นกำลังใจให้ผู้เขียนด้วยนะครับ ใครที่มีคำถามสงสัย อยากพูดคุย เสนอเรื่องที่อยากให้ผมเขียนเกี่ยวกับ Firebase ก็สามารถ Comment ผ่านทั้งตัวบทความและ Post ถามทางกลุ่ม Facebook Group : Firebase Developer Group Thailand ได้จ้า

--

--

Thanongkiat Tamtai
Firebase Thailand

CTO @ Flagfrog # Full-stack Developer # Everything i can do , but it maybe not cool