Logo

การใช้งาน Cloud Firestore กับ Python: สำหรับผู้เริ่มต้น

avatar
Wuttichai Kaewlomsap
firestore

Cloud Firestore คืออะไร?

Cloud Firestore เป็นบริการฐานข้อมูลแบบ NoSQL ใน Firebase ที่ออกแบบมาเพื่อรองรับแอปพลิเคชันที่ต้องการประสิทธิภาพสูงและความสามารถในการซิงค์ข้อมูลแบบเรียลไทม์ มีจุดเด่นคือ:

  • การจัดเก็บข้อมูลในรูปแบบเอกสาร (Document-Oriented)
  • รองรับการซิงค์ข้อมูลระหว่างแอปและเซิร์ฟเวอร์
  • ใช้งานง่ายผ่าน SDK และ REST API
  • เหมาะสำหรับแอปพลิเคชันที่ต้องการขยายระบบได้ง่าย เช่น ระบบแชท เกมออนไลน์ หรือระบบ IoT

Data Model

  1. Collection คือ พื้นที่ใช้เก็บ Documents เปรียบเสมือน Table บน Relational Database เช่น "users", "products" และ "orders" เป็นต้น

  2. Document คือ ข้อมูลที่อยู่ใน Collection มี ID เป็น unique key สามารถเก็บได้ทั้ง fields และ subcollections (1 doc สามารถเก็บได้ทั้ง subcollections และ fields)

    • fields: คือข้อมูลใน document เก็บอยู่ในรูปของ key-value ซึ่งเก็บ datatype ได้ตามปกติเลย เช่น timestamp, number, string, boolean, array, map และ geopoint ที่พิเศษคือมี reference
    • subcollections: คือ collection ที่อยู่ภายใน document (อธิบายเพิ่มเติมในหัวข้อ data structure)
  3. References คือ การอ้างอิงถึงตำแหน่งที่เก็บข้อมูล

    # ตัวอย่าง ref
    users_ref = db.collection("users") # ref to collection
    a_lovelace_ref = db.collection("users").document("alovelace") # ref to document
    a_lovelace_ref = db.document("users/alovelace") # ref to document เหมือนกันกับข้างบน
    

โครงสร้างข้อมูล (Data Structure)

การออกแบบโครงสร้างข้อมูลจะแบ่งตามการใช้งานและความสัมพันธ์ของข้อมูล โดยจะแบ่งได้หลักๆ 3 รูปแบบดังนี้:

  1. Nested object คือ การเก็บข้อมูลไปยัง document ตรงๆ เป็นวิธีง่ายๆ ตรงไปตรงมา นิยมใช้เมื่อจะเก็บข้อมูล dict ทั้งก้อนเฉยๆ ไม่ได้จะไปเพิ่มลดข้อมูลใน dict นั้นๆ หรือไปหาความสัมพันธ์กับข้อมูลอื่นๆ (จริงๆทำได้ แต่ไม่เหมาะ)

  2. Subcollections คือ collections ที่อยู่ภายใต้ document อีกที ซึ่งทำให้เห็นลำดับชั้นข้อมูลได้ชัดเจน จึงเหมาะกับข้อมูล one-to-many relationships เช่น 1 user (user_id=1) สามารถสั่งของได้หลาย order (order_id=4, 5) จะเขียนได้ว่า

    • /users/1/orders/4
    • /users/1/orders/5
    # ตัวอย่างโครงสร้าง Subcollections
    /users/user_id               # document ของผู้ใช้
    /users/user_id/orders        # subcollection เก็บประวัติการสั่งซื้อของผู้ใช้
    /users/user_id/addresses     # subcollection เก็บที่อยู่ของผู้ใช้
    
  3. Root Collections คือ collections จะอยู่ระดับบนสุด รูปแบบนี้จะคล้ายกับ Relational Database ที่สร้างแต่ละ table ตามหมวดหมู่ตัวเอง นิยมใช้กับข้อมูล many-to-many relationships และต้องการ query ข้อมูล เนื่องจากข้อมูลสามารถสร้าง ref ไปยังอีกชุดใน collection อื่นๆได้

    # ตัวอย่างโครงสร้าง Root Collections
    /users        # เก็บข้อมูลผู้ใช้
    /products     # เก็บข้อมูลสินค้า
    /orders       # เก็บข้อมูลคำสั่งซื้อ
    

การติดตั้งและการเตรียมโปรเจค

เตรียม firestore project

จะทำการสร้าง firebase project, create firestore datebase และดาวโหลด credential file สำหรับเชื่อมต่อ firestore ด้วย python

  1. เข้าสู่เว็บไซค์ firebase.google.com และกดไปที่ console ด้านขวาบน
  2. Create a project และทำตามขั้นตอนที่เค้าแจ้ง
  3. สร้าง database: ไปที่แถบ Navigation Bar ใต้ Product categories ให้เลือก Build > Firestore Database และทำการสร้าง database
  4. โหลด credential json file: ไปที่แถบ Navigation Bar > กดเฟืองข้างๆ Project Overview > Project settings > Service accounts > Firebase Admin SDK > Generate new private key และนำไฟล์ credential วางไว้ใน folder ที่จะทำงานได้เลย

ติดตั้ง firebase-admin บน python

  1. เปิดโปรแกรม VsCode สำหรับเตรียมเขียน code
  2. เปิด Terminal และติดตั้ง Firebase Admin SDK ผ่าน pip:
pip install firebase-admin python-dotenv

เชื่อมต่อ Firestore ด้วย Python

ตัวอย่าง code การเชื่อมต่อ Firestore ด้วย python โดยมีอ่าน path credential จากไฟล์ .env และทำการ init app เท่านี้ก็จะเชื่อม Firebase เรียบร้อย และพร้อมที่จะใช้ firestore แล้ว

# .env
FIREBASE_CREDENTIALS_PATH=./path/to/serviceAccountKey.json
import os
from firebase_admin import firestore
from firebase_admin import credentials, initialize_app
from dotenv import load_dotenv

load_dotenv(".env", override=True)

FIREBASE_CREDENTIALS_PATH = os.getenv("FIREBASE_CREDENTIALS_PATH")

cred = credentials.Certificate(FIREBASE_CREDENTIALS_PATH)

try:
    default_app = initialize_app(cred)
except:
    pass

db = firestore.client()

ตัวอย่างการใช้งานพื้นฐาน (CRUD Operations)

  • set: จะเป็นการเขียนข้อมูลไปยัง ref ที่กำหนด ถ้ามีอยู่แล้วจะเป็นการเขียนทับ
  • get: ใช้สำหรับการดึงข้อมูลจาก ref ที่กำหนด
  • update: จะ update เฉพาะ key ที่เรากำหนด
  • delete: ลดข้อมูลที่ตำแหน่ง ref ที่กำหนด

1. การสร้างข้อมูล (Create)

def create_user(user_id: str, user_data: dict) -> dict:
    """
    สร้างผู้ใช้ใหม่ในระบบ
    """
    try:
        doc_ref = db.collection('users').document(user_id)
        doc_ref.set(user_data)
        return {"message": f"สร้างผู้ใช้ {user_id} สำเร็จ"}
    except Exception as e:
        return {"error": f"เกิดข้อผิดพลาด: {str(e)}"}

# ตัวอย่างการใช้งาน
user_data = {
    "name": "สมชาย ใจดี",
    "age": 25,
    "email": "[email protected]"
}
create_user("user123", user_data)

2. การอ่านข้อมูล (Read)

def get_user(user_id: str) -> dict:
    """
    ดึงข้อมูลผู้ใช้จาก ID
    """
    try:
        doc_ref = db.collection('users').document(user_id)
        doc = doc_ref.get()
        if doc.exists:
            doc_dict = doc.to_dict()
            doc_dict["id"] = doc.id
            return doc_dict
        return {"error": "ไม่พบผู้ใช้"}
    except Exception as e:
        return {"error": f"เกิดข้อผิดพลาด: {str(e)}"}

user = get_user("user123")

3. การอัพเดทข้อมูล (Update)

def update_user(user_id: str, update_data: dict) -> dict:
    """
    อัพเดทข้อมูลผู้ใช้
    """
    try:
        doc_ref = db.collection('users').document(user_id)
        doc_ref.update(update_data)
        return {"message": f"อัพเดทข้อมูลผู้ใช้ {user_id} สำเร็จ"}
    except Exception as e:
        return {"error": f"เกิดข้อผิดพลาด: {str(e)}"}

# ตัวอย่างการใช้งาน
update_data = {
    "age": 26,
    "phone": "081-234-5678"
}
update_user("user123", update_data)

4. การลบข้อมูล (Delete)

def delete_user(user_id: str) -> dict:
    """
    ลบข้อมูลผู้ใช้
    """
    try:
        doc_ref = db.collection('users').document(user_id)
        doc_ref.delete()
        return {"message": f"ลบผู้ใช้ {user_id} สำเร็จ"}
    except Exception as e:
        return {"error": f"เกิดข้อผิดพลาด: {str(e)}"}

Usecase การใช้งานอื่นๆ

อ่านข้อมูลทั้ง collection

def get_products(store_id: str) -> list:
    products_ref = db.collection("stores").document(str(store_id)).collection("products")
    product_docs = products_ref.get()

    if not product_docs:
        return None

    product_list = []
    for product_doc in product_docs:
        product_dict = product_doc.to_dict()
        product_dict["id"] = product_doc.id
        product_list.append(product_dict)

    return product_list

1. ตรวจสอบข้อมูล (Validation) + CRUD

def validate_document_reference(doc_ref):
    """Validate if the document reference exists."""
    if not isinstance(doc_ref, firestore.DocumentReference):
    raise ValueError("Provided reference is not a DocumentReference.")

        doc = doc_ref.get()
        if not doc.exists:
            raise ValueError(f"Document {doc_ref.id} does not exist.")

        return doc.to_dict()

def retrieve_data(collection_name, document_id):
    """Retrieve data from a Firestore document."""
    doc_ref = db.collection(collection_name).document(document_id)
    return validate_document_reference(doc_ref)

def set_data(collection_name, document_id, data):
    """Set data in a Firestore document."""
    doc_ref = db.collection(collection_name).document(document_id)
    doc_ref.set(data)
    return f"Document {document_id} successfully set in {collection_name}."

def get_root_collection(collection_name):
    """Get all documents from the root collection."""
    collection_ref = db.collection(collection_name)
    docs = collection_ref.get()

    return {doc.id: doc.to_dict() for doc in docs}

def get_subcollection(parent_collection, parent_id, subcollection_name):
    """Get all documents from a subcollection."""
    subcollection_ref = db.collection(parent_collection).document(parent_id).collection(subcollection_name)
    docs = subcollection_ref.get()

    return {doc.id: doc.to_dict() for doc in docs}

# Example usage

if __name__ == "__main__": # Set some example data
    set_data("users", "user_1", {"name": "Alice", "age": 30})

    # Retrieve user data
    user_data = retrieve_data("users", "user_1")
    print("User Data:", user_data)

    # Get all users
    all_users = get_root_collection("users")
    print("All Users:", all_users)

    # Set up subcollections and retrieve them
    set_data("users", "user_1", {"orders": []})  # Assuming you want to initialize orders for user_1
    set_data("users/user_1/orders", "order_1", {"item": "Book", "quantity": 2})

    orders = get_subcollection("users", "user_1", "orders")
    print("User Orders:", orders)

คำอธิบายโค้ด

  1. validate_document_reference(doc_ref): ทำการตรวจสอบว่าการอ้างอิง (reference) ที่ส่งเข้ามาเป็น DocumentReference จริงหรือไม่ ตรวจสอบว่าเอกสารนั้นมีอยู่จริงใน Firestore หรือไม่
  2. retrieve_data(collection_name, document_id): ดึงข้อมูลจากเอกสารที่ระบุมีการตรวจสอบความถูกต้องของการอ้างอิงก่อนดึงข้อมูล
  3. set_data(collection_name, document_id, data): ใช้สำหรับกำหนดหรืออัปเดตข้อมูลในเอกสารที่ระบุ
  4. get_root_collection(collection_name): ดึงข้อมูลทั้งหมดจาก root collection ที่ระบุ
  5. get_subcollection(parent_collection, parent_id, subcollection_name): ดึงข้อมูลทั้งหมดจาก subcollection ที่อยู่ภายใต้เอกสารแม่ (parent document)

เทคนิคและคำแนะนำ

  1. การใช้ Environment Variables: ควรเก็บข้อมูลที่สำคัญเช่น API keys หรือ credentials ไว้ในไฟล์ .env เสมอ
  2. การจัดการข้อผิดพลาด: ใช้ try-except เพื่อจัดการกรณีที่อาจเกิดข้อผิดพลาด เช่น เมื่อไม่พบเอกสารที่ต้องการ
  3. การออกแบบโครงสร้างข้อมูล: ออกแบบโครงสร้างฐานข้อมูลให้เหมาะสม ใช้ collections และ subcollections อย่างมีประสิทธิภาพ

สรุป

Firebase Firestore กับ Python เปิดโอกาสให้คุณสร้างแอปพลิเคชันที่ทรงพลังด้วยการซิงค์ข้อมูลแบบเรียลไทม์ ด้วยการทำตามขั้นตอนการติดตั้งและใช้งานฟังก์ชันที่ให้ไว้ คุณสามารถเริ่มพัฒนาแอปพลิเคชันของคุณโดยใช้ Firestore เป็นฐานข้อมูลได้อย่างรวดเร็ว

คำถามที่พบบ่อย (FAQs)

การคิดค่าใช้จ่ายของ Firestore เป็นอย่างไร?

  • จำนวนการอ่าน/เขียน/ลบเอกสาร
  • ปริมาณข้อมูลที่จัดเก็บ
  • ปริมาณการรับส่งข้อมูล
  • มีแพ็คเกจฟรีสำหรับการเริ่มต้นใช้งาน

ข้อจำกัดของ Firestore มีอะไรบ้าง?

  • ขนาด document สูงสุด 1 MB
  • จำนวน subcollections ไม่จำกัด แต่แนะนำไม่เกิน 100 ระดับ
  • การ query มีข้อจำกัดบางประการ เช่น ไม่สามารถ join collections ได้โดยตรง

Reference

avatar

Wuttichai Kaewlomsap

Data Engineer