Cấu trúc cơ sở dữ liệu của bạn

Hướng dẫn này đề cập đến một số khái niệm chính trong cấu trúc dữ liệu và phương pháp để xây dựng cấu trúc dữ liệu JSON trong Cơ sở dữ liệu theo thời gian thực Firebase.

Để xây dựng một cơ sở dữ liệu có cấu trúc phù hợp, bạn phải đầu tư nhiều công sức. Điều quan trọng nhất là bạn cần lập kế hoạch về cách dữ liệu sẽ được lưu và truy xuất sau này để quá trình đó trở nên dễ dàng nhất có thể.

Cách dữ liệu có cấu trúc: đó là cây JSON

Tất cả dữ liệu trong Cơ sở dữ liệu theo thời gian thực của Firebase đều được lưu trữ dưới dạng đối tượng JSON. Bạn có thể nghĩ đến cơ sở dữ liệu dưới dạng cây JSON được lưu trữ trên đám mây. Không giống như cơ sở dữ liệu SQL, không có bảng hoặc bản ghi. Khi bạn thêm dữ liệu vào cây JSON, dữ liệu đó sẽ trở thành một nút trong cấu trúc JSON hiện tại với một khoá liên kết. Bạn có thể cung cấp khoá của riêng mình, chẳng hạn như mã nhận dạng người dùng hoặc tên ngữ nghĩa hoặc có thể được cung cấp cho bạn bằng cách sử dụng push().

Nếu bạn tạo khoá của riêng mình, thì các khoá đó phải được mã hoá UTF-8, có thể là tối đa có kích thước 768 byte và không thể chứa bộ điều khiển ., $, #, [, ], / hoặc ASCII ký tự 0-31 hoặc 127. Bạn không thể sử dụng các ký tự điều khiển ASCII trong các giá trị chính họ.

Ví dụ: hãy xem xét một ứng dụng trò chuyện cho phép người dùng lưu trữ và danh bạ của bạn. Hồ sơ người dùng điển hình nằm tại một đường dẫn, chẳng hạn như /users/$uid. Người dùng alovelace có thể có mục nhập cơ sở dữ liệu sẽ có dạng như sau:

{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      "contacts": { "ghopper": true },
    },
    "ghopper": { ... },
    "eclarke": { ... }
  }
}

Mặc dù cơ sở dữ liệu sử dụng cây JSON nhưng dữ liệu được lưu trữ trong cơ sở dữ liệu có thể được biểu thị dưới dạng một số kiểu gốc nhất định tương ứng với các loại JSON có sẵn để giúp bạn viết mã dễ bảo trì hơn.

Các phương pháp hay nhất cho cấu trúc dữ liệu

Tránh lồng dữ liệu

Vì Cơ sở dữ liệu theo thời gian thực của Firebase cho phép lồng dữ liệu sâu tới 32 cấp, bạn có thể nghĩ rằng đây nên là cấu trúc mặc định. Tuy nhiên, khi tìm nạp dữ liệu tại một vị trí trong cơ sở dữ liệu, bạn cũng truy xuất tất cả các nút con. Ngoài ra, khi bạn cấp cho ai đó quyền đọc hoặc ghi tại một nút trong cơ sở dữ liệu, bạn cũng cấp cho họ quyền truy cập vào tất cả dữ liệu trong nút. Do đó, trong thực tế, tốt nhất bạn nên giữ cấu trúc dữ liệu của mình phẳng nhất có thể.

Để xem ví dụ về lý do khiến dữ liệu lồng ghép không tốt, hãy cân nhắc những điều sau cấu trúc lồng nhau nhân:

{
  // This is a poorly nested data architecture, because iterating the children
  // of the "chats" node to get a list of conversation titles requires
  // potentially downloading hundreds of megabytes of messages
  "chats": {
    "one": {
      "title": "Historical Tech Pioneers",
      "messages": {
        "m1": { "sender": "ghopper", "message": "Relay malfunction found. Cause: moth." },
        "m2": { ... },
        // a very long list of messages
      }
    },
    "two": { ... }
  }
}

Với thiết kế lồng nhau này, việc lặp lại qua dữ liệu sẽ trở nên khó khăn. Cho ví dụ: việc liệt kê tiêu đề của cuộc trò chuyện yêu cầu toàn bộ chats cây, bao gồm tất cả thành viên và thư, được tải xuống máy khách.

Làm phẳng cấu trúc dữ liệu

Nếu dữ liệu được chia thành các đường dẫn riêng biệt (còn được gọi là huỷ chuẩn hoá), tệp có thể được tải xuống hiệu quả trong các lệnh gọi riêng biệt khi cần. Cân nhắc cấu trúc phẳng này:

{
  // Chats contains only meta info about each conversation
  // stored under the chats's unique ID
  "chats": {
    "one": {
      "title": "Historical Tech Pioneers",
      "lastMessage": "ghopper: Relay malfunction found. Cause: moth.",
      "timestamp": 1459361875666
    },
    "two": { ... },
    "three": { ... }
  },

  // Conversation members are easily accessible
  // and stored by chat conversation ID
  "members": {
    // we'll talk about indices like this below
    "one": {
      "ghopper": true,
      "alovelace": true,
      "eclarke": true
    },
    "two": { ... },
    "three": { ... }
  },

  // Messages are separate from data we may want to iterate quickly
  // but still easily paginated and queried, and organized by chat
  // conversation ID
  "messages": {
    "one": {
      "m1": {
        "name": "eclarke",
        "message": "The relay seems to be malfunctioning.",
        "timestamp": 1459361875337
      },
      "m2": { ... },
      "m3": { ... }
    },
    "two": { ... },
    "three": { ... }
  }
}

Giờ đây, bạn có thể lặp lại danh sách các phòng bằng cách chỉ tải một vài byte cho mỗi cuộc trò chuyện, nhanh chóng tìm nạp siêu dữ liệu để liệt kê hoặc hiển thị trong một giao diện người dùng. Tin nhắn có thể được tìm nạp riêng và xuất hiện khi tin nhắn đến. cho phép giao diện người dùng thích ứng và nhanh chóng.

Tạo dữ liệu có thể điều chỉnh theo tỷ lệ

Khi tạo ứng dụng, tốt hơn hết là bạn nên tải xuống một nhóm nhỏ danh sách. Điều này đặc biệt phổ biến nếu danh sách chứa hàng nghìn bản ghi. Khi mối quan hệ này là tĩnh và một chiều, bạn chỉ cần lồng các đối tượng con trong cấp độ mẹ.

Đôi khi, mối quan hệ này linh động hơn hoặc có thể cần phải huỷ chuẩn hoá dữ liệu này. Nhiều lần bạn có thể huỷ chuẩn hoá dữ liệu bằng cách sử dụng một truy vấn để truy xuất một tập hợp con dữ liệu, như được thảo luận trong Sắp xếp và lọc dữ liệu.

Nhưng ngay cả như vậy có thể vẫn chưa đủ. Ví dụ: hãy xem xét mối quan hệ hai chiều giữa người dùng và nhóm. Người dùng có thể thuộc về một nhóm và các nhóm bao gồm một danh sách người dùng. Đến lúc phải quyết định xem người dùng thuộc về nhóm nào, mọi thứ trở nên phức tạp.

Mục cần thiết là một cách tinh tế để liệt kê các nhóm chứa người dùng và chỉ tìm nạp dữ liệu cho các nhóm đó. Một chỉ mục của nhóm có thể giúp có nhiều ưu đãi hấp dẫn ở đây:

// An index to track Ada's memberships
{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      // Index Ada's groups in her profile
      "groups": {
         // the value here doesn't matter, just that the key exists
         "techpioneers": true,
         "womentechmakers": true
      }
    },
    ...
  },
  "groups": {
    "techpioneers": {
      "name": "Historical Tech Pioneers",
      "members": {
        "alovelace": true,
        "ghopper": true,
        "eclarke": true
      }
    },
    ...
  }
}

Bạn có thể nhận thấy rằng thao tác này sẽ sao chép một số dữ liệu bằng cách lưu trữ mối quan hệ dưới cả hồ sơ của Ada và trong nhóm. Hiện tại, alovelace được lập chỉ mục trong nhóm, và techpioneers được liệt kê trong hồ sơ của Ada. Vì vậy, để xoá Ada của nhóm, nó phải được cập nhật ở hai nơi.

Đây là phần dư thừa cần thiết cho mối quan hệ hai chiều. Công cụ này cho phép bạn tìm nạp các thành viên của Ada một cách nhanh chóng và hiệu quả, ngay cả khi danh sách người dùng hoặc của các nhóm mở rộng quy mô theo hàng triệu hoặc khi các quy tắc bảo mật của Cơ sở dữ liệu thời gian thực ngăn quyền truy cập vào một số bản ghi.

Phương pháp này, đảo ngược dữ liệu bằng cách liệt kê ID dưới dạng khoá và đặt giá trị thành true, giúp việc kiểm tra khoá cũng đơn giản như việc đọc /users/$uid/groups/$group_id rồi kiểm tra xem đó có phải là null hay không. Chỉ mục nhanh hơn và hiệu quả hơn nhiều so với việc truy vấn hoặc quét dữ liệu.

Các bước tiếp theo