ออกแบบโครงสร้างฐานข้อมูลของ Instagram โดยใช้ Cloud Firestore ตั้งแต่เริ่มต้น

Thanongkiat Tamtai
Firebase Thailand
Published in
8 min readNov 4, 2017
https://firebase.google.com/products/firestore/

ต่อจากที่เคยพูดไว้ในบทความที่แล้วว่าเราจะมาลองออกแบบโครงสร้างของฐานข้อมูลโดยใช้ Cloud Firestore กันซึ่งเป็นฐานข้อมูลแบบ NoSQL โดยตัวอย่างของสิ่งที่เราจะลองออกแบบกัน เราก็จะเลือกสิ่งที่ใกล้ๆตัวเราเพื่อที่จะทำให้ทุกคนได้เห็นภาพนั้นคือ Instagram นั้นเองซึ่งเราเกือบทุกคนก็คุ้นชินกับมันและก็ยังมี Scope ที่ไม่ใหญ่จนเกินไป ทำให้ทุกๆคนสามารถมองเห็นภาพรวมและนำไปประยุกต์กับการออกแบบฐานข้อมูลของตนเองได้

โครงสร้างของฐานข้อมูลใน Cloud Firestore

ย้อนกลับไปพื้นฐานของโครงสร้างของฐานข้อมูลใน Cloud Firestore กัน ใครที่ยังไม่รู้ว่า Cloud Firestore นั้นเก็บข้อมูลในรูปแบบไหนบ้าง เช่น Key-Value , Field , Document , Collection , Subcollection ให้ลองกลับไปอ่านบทความ เข้มข้นกับ Firebase Cloud Firestore ระบบฐานข้อมูลที่เปิดตัวใหม่ล่าสุดจาก Firebase แบบจัดเต็ม เป็นพื้นฐานก่อนนะครับ เพราะในบทความนี้เราจะเข้าเรื่องโครงสร้างของฐานข้อมูลที่เคยอธิบาย ประเภทโครงสร้างของฐานข้อมูล กันคร่าวๆไปในบทความที่แล้วกันเลย

ประเภทโครงสร้างของฐานข้อมูล

ในโครงสร้างของฐานข้อมูลของ Cloud Firestore จะแบ่งออกเป็น 3 ประเภทใหญ่ๆ ได้แก่ Nested data in documents , Subcollections , Root-level collections โดยที่แต่ละชนิดก็จะมีข้อดีข้อจำกัดแตกต่างกันไป เราควรที่เลือกเพื่อให้เหมาะกับสถานการณ์การใช้งานของเรา

Nested data in documents

Nested data in documents

การออกแบบโดยใช้ Document เพียงอย่างเดียว ซึ่งถ้าหากข้อมูลมีความซับซ้อนก็จะใช้วิธีสร้าง Objects หรือ Array

  • ข้อดี คือ ถ้าเรามีการเก็บข้อมูลที่ง่ายๆ ไม่มีความสัมพันธ์อะไรมาก และข้อมูลที่เราจะซ้อนกันเข้าไปนั้นก็มีจำนวนไม่มาก หรือ เรารู้จำนวนที่แน่นอนอยู่แล้ว ก็สามารถใช้วิธีเก็บข้อมูลแบบนี้ได้ เพราะการเพิ่มข้อมูลอะไรลงไปทำได้ง่าย ข้อมูลทุกอย่างก็อยู่ภายใน Document ตัวมันเองทั้งหมด ทำให้เวลาที่เราอยากจะลบ Document กับข้อมูลที่ซับซ้อนอยู่ภายในก็สามารถจะลบแค่ที่ Document เพียงจุดเดียว
  • ข้อจำกัด คือ เราจะไม่สามารถสอบถามข้อมูล(Query) ที่อยู่ภายในส่วนที่ซับซ้อนได้ และในอนาคตหากข้อมูลเรามีขยายหรือเติบโต เราจะไปเปลี่ยนเป็นชนิดอื่นจะทำได้ยาก อีกอย่างหนึ่งก็คือ การที่เราเก็บข้อมูลไว้ภายใน Objects มากๆ ก็จะทำในการร้องขอข้อมูลหรือการสอบถามข้อมูลเป็นไปได้ช้าลงด้วยเพราะข้อมูลของเรานั้นมีขนาดที่ใหญ่ภายใน Document เดียว
  • ควรจะใช้ตอนไหนดี ? เช่น หากเราทำแอพ Chat แล้วเราอยากจะเก็บ 3 ชื่อห้องที่คุยล่าสุดไว้ใน Document Profile ของ User

Subcollections

Subcollections

การออกแบบโดยการสร้าง Collection ไว้ใน Document ซึ่งเรียกว่า Subcollection เมื่อเรามีข้อมูลที่ต้องการขยายมากขึ้นเรื่อยๆในอนาคต

  • ข้อดี คือ หากข้อมูลของเรามีการโตหรือขยายมากขึ้นในอนาคต ขนาดของ Document หลักก็ยังมีขนาดเท่าเดิมและเรายังสามารถสอบถามข้อมูลได้อย่างเต็มรูปแบบ
  • ข้อจำกัด คือ การลบข้อมูลจะทำได้ลำบากเพราะการที่ลบ Document หลักภายใน Subcollection จะไม่ถูกลบไปด้วย ทำให้เราต้องไปจัดการในส่วนนี้ด้วยตัวเอง ซึ่งกฏของการลบ Collection หรือ Subcollection คือ ต้องไม่มี Document เหลืออยู่ภายในนั้นเลยจึงจะถือว่าเป็นการลบ Collection หรือ Subcollection อีกข้อจำกัดก็คือ เราจะไม่สามารถสอบถามข้อมูลระหว่าง Subcollection ของแต่ละ Document ได้
  • ควรจะใช้ตอนไหนดี ? เช่น หากเราทำแอพ Chat แล้วอยากจะสร้าง Collection ของ Users หรือ messages ไว้ใน Document ของห้องคุย

Root-level collections

การสร้าง Collection หลายๆอันไว้ที่ระดับบนสุด ถ้าหากเรามีส่วนของชุดข้อมูลที่แตกต่างกัน

  • ข้อดี คือ หากใช้วิธีนี้ฐานข้อมูลของเราจะมีความยืดหยุ่นและรับรองการขยายตัวของข้อมูลมากที่สุดและทำการสอบถามข้อมูลได้อย่างมีประสิทธิภาพภายใน Collection แต่ละตัว
  • ข้อจำกัด คือ เมื่อฐานข้อมูลมีขนาดโตขึ้นอาจจะทำให้ลำดับชั้นของข้อมูลมากขึ้นเรื่อยๆ
  • ควรจะใช้ตอนไหนดี ? เช่น หากเราทำแอพ Chat แล้วอยากจะสร้าง Collection ของแต่ละ Users และของแต่ละห้องพูดคุยที่ภายในเก็บข้อมูล messages ของแต่ละห้องไว้ใน Document ของห้องคุย

การออกแบบฐานข้อมูลของ Instagram

หลังจากเรารู้ว่าแต่ละชนิดของการออกแบบมีข้อดีข้อจำกัดอย่างไรบ้าง ? ที่นี้เราลองมาดูขั้นตอนการออกแบบฐานข้อมูลกัน

  • การศึกษาสิ่งที่เรากำลังจะทำ
  • เก็บรวบรวมข้อมูลรายละเอียด
  • ออกแบบโครงสร้างเริ่มจากส่วนย่อยๆ
  • จัดการความสัมพันธ์ของข้อมูล

การศึกษาสิ่งที่เรากำลังจะทำ

ในขั้นตอนนี้เราจะมาลองดูกันว่ารายละเอียดต่างๆของแต่ละหน้าหลักๆ ของแอพ Instagram มีอะไรกันบ้าง

หน้า Home

Home
  • ส่วนของ Story อยู่ทางด้านบนที่จะนำเรื่องราวของคนที่เราทำการ Following ได้ Post เนื้อหาของแต่ละวันอะไรลงไปบ้าง
  • ส่วนของ Feed อยู่ทางด้านล่างที่จะนำรูปหรือวิดีโอของคนที่เราทำการ Following ได้ Post เนื้อหามาแสดงอยู่ในส่วนนี้

หน้า Search

  • จะมีการแนะนำ Story ของคนที่ดังๆ และ เราไม่ได้ Following อยู่ทางด้านบน
  • ด้านล่างจะมีการแนะนำ Post ทั้งรูปภาพและวิดีโอของคนที่ดังๆ และ เราไม่ได้ Following
  • ด้านบนสุดจะมี Tab ค้นหาให้เราสามารถค้นหาผู้ใช้คนอื่นๆ และยังมีการเก็บประวัติค้นหาล่าสุดของเราไว้

หน้า Post

  • จะมีให้เราได้ทำการเลือกรูปถ่ายหรือวิดีโอที่มีอยู่แล้วในเครื่องหรือเราจะทำการถ่ายใหม่ ณ ขณะนั้นก็สามารถทำได้

หน้า Notification

  • จะมีการแจ้งเตือนของเราทั้งหมดอยู่ในส่วนนี้และการแจ้งเตือนสาธารณะของผู้ใช้ที่เราไปทำการ Following

หน้า Profile

  • จะเป็นหน้าแสดงรายละเอียดข้อมูลต่างๆของเราเองหรือหากเราเข้าไปดูหน้า Profile ของคนอื่น ก็จะแสดงรายละเอียดคล้ายๆกัน

เก็บรวบรวมข้อมูลรายละเอียด

ต่อมาเราจะรวบรวมรายละเอียดต่างๆในส่วนย่อยๆ เช่น Story , Post , Comment , Reply , Notification , Profile

รายละเอียดของ Story ประกอบไปด้วย

  • Username
  • Profile Image
  • Video Link
  • Image Link

รายละเอียดของ Post ประกอบไปด้วย

  • Username
  • Profile Image
  • Video Link
  • Image Link
  • Text
  • Like Count
  • Comment Count
  • Share Count
  • Video Watch Count
  • Date

รายละเอียดของ Comment ประกอบไปด้วย

  • Username
  • Profile Image
  • Text
  • Like Count
  • Reply Count
  • Date

รายละเอียดของ Reply ประกอบไปด้วย

  • Username
  • Profile Image
  • Text
  • Like Count
  • Date

รายละเอียดของ Notification ประกอบไปด้วย

  • Username
  • Profile Image
  • Count
  • Date
  • Text
  • Type
  • Thumbnail Link

รายละเอียดของ Profile ประกอบไปด้วย

  • Username
  • Profile Image
  • Post Count
  • Following Count
  • Follower Count
  • Biography
  • Thumbnail Link

ออกแบบโครงสร้างเริ่มจากส่วนย่อยๆ

มาถึงขั้นตอนที่เราจะนำเอาข้อมูลที่เราศึกษาและเก็บรวบรวมรายละเอียดมาวางเป็นโครงสร้างของฐานข้อมูลกันดู โดยที่เราต้องเข้าใจก่อนว่า การออกแบบโครงสร้างของฐานข้อมูลนั้นไม่มีถูก ไม่มีผิด แต่จะอยู่ที่ว่าเหมาะสมกับการใช้งานของเราหรือไม่และความสะดวกในการ เพิ่ม , ลบ , นำข้อมูลออกมาแสดงผล , การสอบถามข้อมูลต่างๆ นั้นสะดวกเพียงใด เพราะอย่างที่ได้เคยกล่าวไปในบทความที่แล้วว่า

ถ้าหากเราออกแบบโครงสร้างไม่ดีนั้นก็จะทำการทำงานของเรายากขึ้นตามไปด้วย

โดยการออกแบบโครงสร้างนั้นจะมีทั้งส่วนที่เรามองเห็นจากภายนอกเป็นรูปธรรมเช่น ข้อมูลของ Post หรือ จะเป็นการออกแบบที่จะเอาไว้ใช้ประโยชน์ในด้านอื่นๆ เช่น ในด้านความสัมพันธ์ของข้อมูล , ในด้านการตรวจสอบความถูกต้องของข้อมูล เป็นต้น

อย่างไรก็ตามทาง Firebase ก็ได้คำแนะนำให้เราทำการออกแบบให้ Flat และ Denormalize คือ เวลาที่เราไปร้องขอข้อมูลจากที่ใดๆ เราควรจะได้ข้อมูลที่พร้อมนำไปแสดงผลทั้งหมด ไม่ใช่กรณีที่เราต้องรอข้อมูลในผลลัพธ์จากการร้องขอครั้งที่ 1 และนำข้อมูลบางส่วนของผลลัพธ์นั้นไปเป็นข้อมูลที่จะต้องเรียกใช้ในการร้องขอครั้งที่ 2 ถึงจะได้ข้อมูลไปแสดงผลครบถ้วน และไม่ต้องกลัวที่จะมีข้อมูลซ้ำๆกันภายในฐานข้อมูล เพราะทาง Firebase นั้นต้องการให้เราสามารถดึงข้อมูลออกไปใช้ให้ง่ายที่สุด โดยในคำแนะนำนี้ก็เป็นคำแนะนำรวมทั้งฐานข้อมูลแบบ Realtime Database และ Cloud Firestore ด้วยแต่ใน Cloud Firestore จะสามารถซ้อนทับของข้อมูลลงไปได้ (Nested) แต่ยังไงเราไม่ต้องทำตามคำแนะนำนี้เสมอไปก็ได้ ขอให้เราออกแบบฐานข้อมูลให้เหมาะสมกับการใช้งานของเรามากที่สุดก็พอ

ทีนี้รูปแบบการออกแบบโครงสร้างของเราที่จะใช้ เราจะใช้ทั้ง 3 รูปแบบเลย บางจุดก็จะเป็น Nested data in documents บางจุดก็ใช้ Subcollections หรือบางจุด Root-level collections โดยจะอธิบายเหตุผลที่จะใช้ในแต่ละส่วนกันไป

มาเริ่มจากส่วนเล็กๆกันก่อนดีกว่า

Story

+story
>storykey // Auto-generate Key ของแต่ละ Story
username (string) // ชื่อ username ของเจ้าของ story นี้
profileImage (string) // Link รูปของเจ้าของ story นี้
mediaLink (string) // Link ของ สิ่งใน story อาจจะเป็นรูปหรือวิดีโอ
timestamp (timestamp) // เวลาที่เพิ่มข้อมูล
ownerStory (string) // User Key ของเจ้าของ story
watchStoryCount(number) // จำนวนคนดูทั้งหมดของ story

โดยผมจะสมมุติ ลักษณ์ “+” เป็นตัวแทนของ Collection และ “>” เป็นตัวแทนของ Document ส่วนที่ไม่มีอะไรข้างหน้าเลยก็คือชื่อ Field นะครับ โดยในวงเล็บก็จะเป็น Type ของแต่ละ Field

ซึ่งอันนี้เป็นเพียงโครงสร้าง Story เพียงอันเดียว แต่ละคนอาจจะมีหลายๆอัน เราจะค่อยไปจัดการความสัมพันธ์ทีหลัง ซึ่งแต่ละ Story จะมีอายุ 24 ชั่วโมง โดยเราก็สามารถเขียน Cloud Funtion มาตรวจสอบได้จาก timestamp ถ้าเกินเราก็ลบ storykey นี้ทิ้ง

Watch Story

+watchStory
>userkey // User Key ของคนที่มาดู story นี้
username(string) // ชื่อ username ของคนที่มาดู story นี้
profileImage(string) // Link รูปของคนที่มาดู story นี้
timestamp(timestamp) // เวลาที่เพิ่มข้อมูล

ซึ่งอันนี้เป็นเพียงโครงสร้าง Watch Story เพียงคนเดียว แต่ละ Story อาจจะมีคนมาดูหลายคน เราจะค่อยนำไปจัดการความสัมพันธ์ทีหลัง

Post

+post
>postkey // Auto-generate Key ของแต่ละ post
username (string) // ชื่อ username ของเจ้าของ post นี้
profileImage (string) // Link รูปของเจ้าของ post นี้
onwerPost(string) // User Key ของเจ้าของ post
imagePost(string) // Link รูปของ post
videoPost(string) // Link วิดีโอของ post
textPost(string) // คำอธิบายรูปหรือวิดีโอ
likePostCount(number) // จำนวน like ทั้งหมดของ post
commentPostCount(number) // จำนวน comment ทั้งหมดของ post
sharePostCount(number) // จำนวน share ทั้งหมดของ post
watchVideoPostCount(number) // จำนวนครั้งการดูวิดีโอของ post
timestamp(timestamp) // เวลาที่เพิ่มข้อมูล

ซึ่งอันนี้เป็นเพียงโครงสร้าง Post เพียงอันเดียว แต่ละคนอาจจะมีหลาย post เราจะค่อยนำไปจัดการความสัมพันธ์ทีหลัง โดยแต่ละ post อาจจะมีข้อมูลภายใน ไม่เหมือนกัน เช่น บาง Post เป็นรูปก็จะมี imagePost หรือหากเป็นวิดีโอก็จะมี videoPost และ watchVideoPostCount เป็นต้น

Like Post

+likePost
>userkey // User Key ของคนที่มากด like post นี้
username(string) // ชื่อ username ของคนที่มา like post นี้
profileImage(string) // Link รูป ของคนที่มา like post นี้
timestamp(timestamp) // เวลาที่มา ของคนที่มา like post นี้

ซึ่งอันนี้เป็นเพียงโครงสร้าง Like Post เพียงคนเดียว แต่ละ post อาจจะมีคนมากดหลายคน เราจะค่อยนำไปจัดการความสัมพันธ์ทีหลัง

Comment

+comment
>commentKey // Auto-generate Key ของแต่ละ comment
username(string) // ชื่อ username ของเจ้าของ comment นี้
profileImage(string) // Link รูปของเจ้าของ comment นี้
onwerComment(string) // User Key ของเจ้าของ comment
textComment(string) // ข้อความการ Comment
timestamp(timestamp) // เวลาที่เพิ่มข้อมูลการ Comment
likeCommentCount(number) // จำนวนการกด Like Comment นี้ทั้งหมด

หลายละเอียดต่างๆ คล้ายๆกับ Post แต่อาจจะมีรายละเอียดน้อยกว่า ซึ่งนี้เป็นเพียงโครงสร้างของ Comment เดียว แต่ในแต่ละ Post อาจจะมี Comment หลายอัน

Like Comment

+likeComment
>userkey // User Key ของคนที่มากด like comment นี้
username(string) // ชื่อ username ของคนที่มา like comment นี้
profileImage(string) // Link รูป ของคนที่มา like comment นี้
timestamp(timestamp) // เวลาที่มา ของคนที่มา like comment นี้

เป็นโครงสร้างคล้ายๆของ Like Post แต่เปลี่ยนชื่อมาเป็นของ Like Comment โดยมีจุดประสงค์เหมือนกันเป๊ะ

Reply

+reply
>replyKey // Auto-generate Key ของแต่ละ reply
username (string) // ชื่อ username ของเจ้าของ reply นี้
profileImage (string) // Link รูปของเจ้าของ reply นี้
onwerReply (string) // User Key ของเจ้าของ reply
textReply(string) // ข้อความการ reply
timestamp(timestamp) // เวลาที่เพิ่มข้อมูลการ reply
likeReplyCount(number) // จำนวนการกด Like reply นี้ทั้งหมด

ไม่มีไรมาก Copy จาก Comment มาแปะแล้วเปลี่ยนชื่อ

Like Reply

+likeReply
>userkey // User Key ของคนที่มากด like reply นี้
username(string) // ชื่อ username ของคนที่มา like reply นี้
profileImage(string) // Link รูป ของคนที่มา like reply นี้
timestamp(timestamp) // เวลาที่มา ของคนที่มา like reply นี้

เช่นเดิมครับ Copy แปะแล้วเปลี่ยนชื่อ

Notification

+Notification
>notificationKey // Auto-generate Key ของแต่ละ notification
postKeyPath(Reference) // Link ของ Post ที่ถูกแจ้งเตือน
commentKeyPath(Reference) // Link ของ Comment ที่ถูกแจ้งเตือน
replyKeyPath(Reference) // Link ของ Reply ที่ถูกแจ้งเตือน
userKeyPath(Reference) // Link ของ User ที่มา following
username(string) // ชื่อ username ของคนที่มาทำ Action ใดๆ
ownerKey(string) // User Key ของคนที่มาทำ Action ใดๆ
timestamp(timestamp) // เวลาที่เกิดการทำ Action
type(string) // ประเภทของการแจ้งเตือน
profileImage(string) // Link รูปของคนที่มาทำ Action ใดๆ

ซึ่งอันนี้เป็นเพียงโครงสร้าง Notification เพียงอันเดียว แต่ละคนอาจจะมีหลาย Notification เราจะค่อยนำไปจัดการความสัมพันธ์ทีหลัง โดยแต่ละ Notification อาจจะมีข้อมูลภายใน ไม่เหมือนกัน เช่น บาง Notification เป็นการแจ้งเตือนกด Like Post ก็จะมี postKeyPath หรือหากการแจ้งเตือน Comment ก็จะมี commentKeyPath เป็นต้น โดยใน Cloud Firestore จะมี Type ของตัวแปรชนิด Reference ให้เราสามารถระบุ Path ของ Document อื่นๆ ได้โดยง่าย โดย Reference Path ต่างๆ ในการออกแบบของเรา ก็จะมีประโยชน์ตอนที่ผู้ใช้กดจะไปดูรายละเอียดจากการแจ้งเตือนอีกทีก็จะทำให้เราจะนำพาผู้ใช้ไปเข้าถึงข้อมูลที่แท้จริงได้

Search History

+searchHistory
>userkey // User Key ของคนที่เราทำได้ค้นหา
username(string) // ชื่อ username ของคนที่เราทำได้ค้นหา
profileImage(string) // Link รูปของคนที่เราทำได้ค้นหา
timestamp(timestamp) // เวลาที่ได้ทำได้ค้นหา

การเก็บข้อมูลของผู้ที่เราทำการค้นหา ซึ่งเป็นเพียงโครงสร้างของประวัติการค้นหาเพียงบุคคลเดียว

Following

+following
>userkey // User Key ของคนที่มากด following
username(string) // ชื่อ username ของคนที่มา following
profileImage(string) // Link รูป ของคนที่มา following
timestamp(timestamp) // เวลาที่มา following

การเก็บข้อมูลของผู้ติดตามของเรา ก็คล้ายๆ กับกรณีอื่นๆ

Follower

+follower
>userkey // User Key ของคนที่มากด follower
username(string) // ชื่อ username ของคนที่มา follower
profileImage(string) // Link รูป ของคนที่มา follower
timestamp(timestamp) // เวลาที่มา follower

การเก็บข้อมูลของผู้ที่เราติดตามของเร า Copy มาแปะแล้วเปลี่ยนชื่อครับผม

User

+user
>userkey // User Key ของเรา
profileImage(string) // Link รูป Profile ของเรา
username(string) // ชื่อ username ของเรา
postCount(number) // เลขจำนวน Post ทั้งหมดของเรา
followingCount(number) // เลขจำนวน ผู้ติดตาม ทั้งหมดของเรา
followerCount(number) // เลขจำนวน ผู้ที่เราติดตาม ทั้งหมดของเรา
biography(string) // ข้อความประวัติส่วนตัวของเรา
thumbnailPost(object) // Link รูปอย่างย่อ ของแต่ละ Post
{postkey:imageLink(string)}

post(array) // Reference Path ของแต่ละ Post ของเรา
post/postkey(Reference) // ตัวอย่าง Reference Path

story(array) // Reference Path ของแต่ละ story ของเรา
story/storyKey(Reference)
bookmark(array) // Reference Path ของ bookmark ที่เราบันทึกไว้
post/postkey(Reference)

มาถึงตรงนี้ส่วนที่สำคัญและค่อนข้างที่ต้องอธิบายยาว นั้นคือโครงสร้างของ User โดยส่วนด้านบนตั้งแต่ profileImage ถึง biography ก็เป็น field ทั่วไป แต่จะมีส่วนที่พิเศษออกมาก็คือ

  • thumbnailPost เราจะสร้างไว้เพื่อเวลามีคนเข้ามาที่หน้า Profile แล้วแสดงรูปขนาดย่อตรงจุดนี้จะเป็นที่รวม Link รูปขนาดย่อของ Post ของเราไว้ทั้งหมด โดยจะเก็บข้อมูลชนิด Object ที่จะมี Key เป็น postkey และ Value เป็น Link รูปขนาดย่อ หากเราทำการกดเข้าไปดูรายละเอียด ก็จะใช้ค่าใน Key คือ postkey ไปเข้าถึงข้อมูลที่ Path post/postkey
  • post เราจะสร้างไว้เพื่อเวลาที่เวลามีคนเข้ามาที่หน้า Profile แล้วแสดงรูปแบบเรียงต่อกันลงมาเป็น List ซึ่งจะเห็นข้อมูลทั้งหมดของแต่ละ Post โดยจะเก็บข้อมูลชนิด Array ซึ่ง Index ของ Array ก็จะเรียงลำดับไปเรื่อยๆ เช่น 0 , 1 , 2 และแต่ละ Index จะมีค่าเป็น Reference Path ของแต่ละ Post
  • story จุดประสงค์การใช้งานคล้ายๆกับ post แต่ Reference Path จะเปลี่ยนเป็น story ของเรา
  • bookmark โครงสร้างจะคล้ายๆกับ post แต่จุดประสงค์การใช้งานจะเปลี่ยนไปเป็น post ที่เราอยากจะบันทึกเก็บไว้

จัดการความสัมพันธ์ของข้อมูล

หลังจากที่เราออกแบบข้อมูลจากส่วนย่อยๆกันแล้วก็มีบางข้อมูลที่มีความสัมพันธ์กัน เช่น Post กับ Like Post , Post กับ Comment , User กับ Following เป็นต้น

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

Post

  • Post-Like Post
    ในการจัดการความสัมพันธ์ของ Post และ Like Post จะออกแบบโดยใช้โครงสร้างแบบ Subcollection โดยที่ Like Post ที่อยู่ใน Subcollection ของ Post นั้นหมายถึง ข้อมูลภายใน Like Post ก็เป็นข้อมูลที่เป็นของ Post นั้นๆ ซึ่ง 1 Post ก็จะสามารถมีคนมากด Like Post ได้หลายคน
  • Post-Comment
    ในการจัดการความสัมพันธ์ของ Post และ Comment ก็ออกแบบโดยใช้โครงสร้างแบบ Subcollection เหมือนกัน โดยที่แต่ละ Post สามารถมีได้หลาย Comment แต่ 1 Comment สามารถเป็นข้อมูลอยู่ภายในได้เพียง 1 Post ทำให้เรานำ Comment ไปไว้เป็น Subcollection ของข้อมูลใน Document แต่ละ Post
  • Comment-Like Comment
    กรณีเดียวกับ Post-Like Post หากแต่ข้อมูลภายในจะเป็นของคนที่มากด Like Comment นั้นๆ
  • Comment-Reply
    กรณีเดียวกับ Post-Comment หากแต่เปลี่ยนจุดประสงค์ของการใช้งานเป็นเก็บข้อมูล Reply ของแต่ละ Comment
  • Reply-Like Reply
    กรณีเดียวกับ Post-Like Post หากแต่ข้อมูลภายในจะเป็นของคนที่มากด Like Reply นั้นๆ

Story & Notification

  • Story-Watch Story
    ก็จะคล้ายๆกับ Like Post โดยใช้โครงสร้างแบบ Subcollection เพียงแต่เปลี่ยนจุดประสงค์การใช้งาน
  • Notification
    ไม่ได้มีความสัมพันธ์กับใครในโครงสร้างของตัวมันเองจึงไม่มี Subcollection แต่จะไปเป็นข้อมูลภายในของโครงสร้างอื่น

User

  • User-Search History
    จะเป็นการเก็บข้อมูลประวัติการค้นหาของแต่ละผู้ใช้ไว้ภายใน user โดยจะเป็นโครงสร้างชนิด Subcollection โดย 1 User ก็สามารถมีประวัติการค้นหาเก็บไว้ได้หลายคน
  • User-Following
    โครงสร้างชนิด Subcollection เหมือนเดิมแต่เพียงเปลี่ยนจุดประสงค์การใช้งานจาก User-Search History เป็นการเก็บรายชื่อของผู้ที่เราไปติดตามแทน
  • User-Follower
    เหมือนกับ User-Search History และ User-Following โดยเปลี่ยนไปเก็บข้อมูลของผู้ที่มาติดตามเรา
  • activityLog
    เราจะสร้างไว้เพื่อเก็บประวัติว่าเราไปทำอะไรที่ Path ไหนบ้าง เช่น ไปกด Like Post ใดๆ หรือไป Comment Post ใดๆ ซึ่งประโยชน์หลักๆเลยก็คือ ลองสังเกตุว่าในแต่ละส่วนของโครงสร้างที่มี username และ profileImage ติดอยู่ในนั้นด้วยหากเราไปทำ Action ใด ซึ่งหากเรามีการเปลี่ยน ชื่อ username หรือ รูป Profile เราจะสามารถไป Update ข้อมูลของเราได้ตาม Reference Path ที่เราเก็บไว้ หรือจะนำส่วนนี้ไปเป็นการแจ้งเตือนให้ผู้ที่กำลังติดตามเราได้ โดยเราจะเก็บเป็นโครงสร้างชนิด Subcollection ไว้ภายใน User แต่ละคน ซึ่งภายในมีการแบ่งประเภทโดยใช้โครงสร้างชนิด Nested data in documents ซึ่งเราจะเก็บข้อมูลเป็นชนิด Array แต่ละ Index จะเก็บข้อมูลของ Reference path แยกย่อยกันไปตามประเภทที่เราแบ่งไว้

userStory , userNewfeed , userNotification และ survey

userStory
จะเป็นส่วนเชื่อมความสัมพันธ์ของระหว่าง User และ Story โดยสาเหตุที่เราต้องแยกออกมาเป็น Root-level collections ใหม่

จะมีกรณีที่น่าคิดอยู่ 2 กรณี คือ

  1. ทำไมไม่ทำเป็น Nested data in documents เหมือนอย่างเช่น thumbnailPost หรือ bookmark

นั่นก็เพราะว่าหากเรานำไปทำเป็นโครงสร้างชนิด Nested data in documents ภายใน user นั่นจะหมายถึงข้อมูลของเราจะถูกอ่านพร้อมกับการเข้าถึงข้อมูลของ user คนนั้น แต่ข้อมูลที่จะบอกว่า User ของคนใดคนหนึ่งจะสามารถเห็น Story ของใครได้บ้าง เป็นข้อมูลส่วนตัวของเฉพาะแต่ละ User เราจึงไม่ควรที่จะให้ผู้ใช้คนอื่นสามารถเห็นข้อมูลตรงส่วนนี้ขณะที่เข้าถึง Document ของ User พร้อมกับข้อมูลใน Profile user คนนั้นๆ

2. ทำไมไม่ทำเป็น Subcollection เหมือนอย่างเช่น Following หรือ searchHistory

นั่นก็เพราะโครงสร้างภายในของ userStory ที่เราได้ทำการออกแบบจะประกอบไปด้วย 1 User สามารถเข้าถึง Story ของคนที่เราไปติดตามได้หลายคนและแต่ละคนอาจจะมี Story อยู่หลายๆอัน ซึ่งใน following user key เราจะเก็บ Reference Path แต่ละ Story ไว้เป็นข้อมูลชนิด Array

+user
>userkey
+userStory // Subcollection
>followinguserkey(array) // Document
/story/storyKey(reference) // Field

ซึ่งหากเราทำดังรูป จะทำให้เกิดข้อผิดพลาดขึ้นมา เพราะ ชื่อของ Document ไม่สามารถสร้างเป็น Array ได้ และ ชื่อของ Field ไม่สามารถมี ‘/’ ภายในชื่อได้ โดยก็มีวิธีแก้ปัญหาเหมือนกัน เช่นทำดังนี้

+user
>userkey
+userStory
>userkey
followinguserkey(array)
/story/storyKey(reference)

ซึ่งหากเรายังยืนยันว่าจะต้องนำ userStory ไปเป็น Subcollection ให้ได้ ก็จะต้องทำการออกแบบดังรูป แต่ Document ก็จะมีเพียง userkey ของเราเพียง Document เดียว ก็จะเกิดการซ้ำซ้อนของการออกแบบโครงสร้างจนเกินไป เพราะต้องมีการอ้างอิงถึง userkey ของเราถึง 2 ครั้ง โดยที่เราก็รู้ทั้งรู้ว่าเป็น Document ของเรา

วิธีที่แนะนำก็คือเราควรที่จะต้องแยกความสัมพันธ์ระหว่าง User และ Story ออกมาเป็น Root-level collections ใหม่นั้นเอง

userNewfeed
ก็มีสาเหตุที่จะต้องทำ Root-level collections ใหม่ ก็มีเหตุผลเหมือนกับ userStory ทุกประการ โดยที่โครงสร้างภายในก็จะเป็นการแบ่ง Document ไปตาม userkey ของผู้ใช้แต่ละคน และข้างในก็ใช้ postkey เป็นชื่อของ Field โดยจะมี Type เป็น null ซึ่งเราจะนำชื่อของ Field ที่ได้ไปหาข้อมูลรายละเอียดของ Post ที่ Path post/postkey

userNotification
ก็มีสาเหตุที่จะต้องทำ Root-level collections ใหม่ ก็มีเหตุผลเหมือนกับ userStory ทุกประการ โดยที่โครงสร้างภายในก็จะเป็นการแบ่ง Document ไปตาม userkey ของผู้ใช้แต่ละคน ข้างในจะแบ่งเป็น Field ที่เก็บการแจ้งเตือนส่วนตัวของเราไว้เป็นชนิด Array แต่ละ Index จะเก็บ Reference Path ของ notification ไว้ และอีกส่วนจะเป็นการบอกว่าผู้ที่เราได้ทำการติดตามได้ไปทำอะไรบ้าง โดยจะแบ่งเป็น 2 ส่วน คือ หากผู้ที่เราติดตามหลายคนไปกระทำการใดๆ ที่ Post เดียวกัน และ หากผู้ที่เราติดตามคนเดียวไปกระทำการใดๆ ที่หลายๆ Post ซึ่งจะการเก็บข้อมูลเป็นชนิด Array แต่ละ Index จะเก็บ Reference Path ของ activityLog ของผู้ที่เราได้ไปติดตามไว้

survey
ส่วนสุดท้ายของการออกแบบก็จะเป็นที่ส่วนที่จะให้ทุกๆคนเห็นเหมือนกันทั้งหมด คือหน้าที่ก่อนที่เราจะเข้าไป Search ที่จะมีการแนะนำรูปภาพหรือวิดีโอต่างๆ ซึ่งไม่ได้มีความสัมพันธ์ใดๆกับใคร ก็จะแบ่งเป็น Document เป็นประเทศๆ ไป Field ภายในก็เป็นประเภท Array ที่จะเก็บ Reference path ของ Post

สรุป

นี้ก็เป็นไอเดียในการออกแบบโครงสร้างของฐานข้อมูลโดยใช้ Cloud Firestore เพื่อเป็นแนวทางให้กับแต่ละคนได้นำไปปรับใช้ในการออกแบบของตนเอง ซึ่งก็อย่างที่บอกไปข้างบนไว้ว่า ไม่มีถูก ไม่มีผิด มีเพียงเหมาะสมกับการใช้งานของเราหรือไม่ ก็ใช้เราเลือกใช้การออกแบบตามสถานการณ์กันไป อย่างไรก็ดีก็ควรคำนึงถึงความยาก-ง่ายในการสอบถามข้อมูลและความปลอดภัยของข้อมูลกันด้วย

โดยบทความต่อไปเราก็จะมาสร้าง Security & rule ให้กับฐานข้อมูลของเราโดยใช้ตัวอย่างจากฐานข้อมูลที่เราได้ออกแบบในบทความนี้กันต่อ ยังไงก็ฝากติดตามและกดปรบมือเป็นกำลังใจให้ด้วยนะครับ :)

--

--

Thanongkiat Tamtai
Firebase Thailand

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