Membuat Struktur Database Anda

Panduan ini mencakup beberapa konsep utama dalam arsitektur data dan praktik terbaik untuk merancang struktur data JSON dalam Firebase Realtime Database Anda.

Membuat database yang terstruktur dengan baik membutuhkan perencanaan yang matang. Yang terpenting, Anda harus merencanakan bagaimana data akan disimpan dan diambil kembali agar proses tersebut dapat berjalan semudah mungkin.

Metode perancangan struktur data: dengan hierarki JSON

Semua data Firebase Realtime Database disimpan sebagai objek JSON. Anda dapat menganggap database sebagai hierarki JSON yang dihosting di cloud. Tidak seperti database SQL, database tersebut tidak memiliki tabel atau catatan. Ketika Anda menambahkan data ke hierarki JSON, data tersebut akan menjadi node di struktur JSON yang ada dengan kunci terkait. Anda dapat memasukkan kunci Anda sendiri, seperti ID pengguna atau nama semantik. Kunci juga dapat disediakan untuk Anda menggunakan push().

Jika Anda membuat kunci sendiri, kunci tersebut harus berenkode UTF-8, berukuran maksimum 768 byte, dan tidak boleh berisi karakter ., $, #, [, ], /, atau karakter kontrol ASCII 0-31 atau 127. Anda juga tidak dapat menggunakan karakter kontrol ASCII dalam nilai-nilai itu sendiri.

Misalnya, bayangkan sebuah aplikasi chat yang memungkinkan pengguna untuk menyimpan profil dasar dan daftar kontak. Profil pengguna biasanya terletak pada suatu jalur, misalnya /users/$uid. Pengguna alovelace mungkin memiliki entri database yang terlihat seperti ini:

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

Meskipun database ini menggunakan hierarki JSON, data yang tersimpan di database dapat digambarkan sebagai jenis bawaan tertentu yang berkaitan dengan jenis JSON yang sudah ada agar Anda dapat menulis kode yang lebih mudah dikelola.

Praktik terbaik untuk struktur data

Menghindari data bertingkat

Karena Firebase Realtime Database memungkinkan data bertingkat hingga kedalaman 32 tingkat, Anda mungkin berpikir bahwa ini adalah struktur defaultnya. Namun, saat mengambil data di sebuah lokasi dalam database Anda, Anda juga mengambil semua node turunannya. Selain itu, ketika Anda memberikan akses baca atau tulis kepada seseorang pada sebuah node di database Anda, orang tersebut juga akan menerima akses ke semua data di bawah node tersebut. Oleh karena itu, dalam praktiknya, lebih baik buat struktur data Anda serata mungkin.

Untuk memberi gambaran mengapa data bertingkat itu buruk, perhatikan struktur multitingkat berikut:

{
  // 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": { ... }
  }
}

Dengan desain bertingkat ini, iterasi data bisa bermasalah. Misalnya, untuk menampilkan daftar judul percakapan chat, seluruh hierarki chats harus didownload ke klien, termasuk semua anggota dan pesannya.

Meratakan struktur data

Jika data tersebut dibagi ke beberapa jalur, yang disebut juga dengan denormalisasi, data tersebut dapat didownload secara efisien dalam panggilan terpisah, sesuai dengan kebutuhan. Perhatikan struktur yang diratakan ini:

{
  // 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": { ... }
  }
}

Sekarang, iterasi daftar ruang chat dapat dilakukan cukup dengan mendownload beberapa byte untuk setiap percakapan. Dengan begitu, pengambilan metadata untuk menampilkan ruang chat di UI bisa dilakukan dengan cepat. Pesan dapat diambil secara terpisah dan ditampilkan saat pesan tersebut tiba, sehingga UI akan tetap cepat dan responsif.

Membuat data yang dapat diskalakan

Ketika mem-build aplikasi, mendownload subkumpulan daftar sering kali merupakan keputusan yang lebih baik. Praktik ini biasa dilakukan, terutama jika daftar tersebut memuat ribuan catatan. Jika hubungan ini bersifat statis dan satu arah, Anda cukup menempatkan objek turunan di bawah induk.

Terkadang, hubungan ini bersifat lebih dinamis, atau denormalisasi mungkin perlu dilakukan pada data ini. Biasanya, denormalisasi data dapat dilakukan dengan menggunakan kueri untuk mengambil subkumpulan data, seperti yang dibahas dalam bagian Mengambil Data.

Akan tetapi, ini pun mungkin tidak cukup. Bayangkan, misalnya, hubungan dua arah antara pengguna dan grup. Pengguna dapat menjadi anggota sebuah grup, sementara grup memiliki daftar sejumlah pengguna. Keadaan menjadi rumit saat Anda harus menentukan grup mana saja yang diikuti pengguna.

Tentu diperlukan cara yang praktis untuk membuat daftar grup yang diikuti pengguna dan hanya mengambil data untuk grup tersebut. Indeks grup dapat sangat membantu di sini:

// 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
      }
    },
    ...
  }
}

Anda mungkin menyadari bahwa tindakan ini akan menduplikat beberapa data dengan cara menyimpan hubungan, baik di catatan Ada maupun di grup. Sekarang alovelace diindeks dalam grup dan techpioneers tercantum pada profil Ada. Jadi, untuk menghapus Ada dari grup, update harus dilakukan di dua tempat.

Ini adalah hal yang harus dilakukan untuk hubungan dua arah. Dengan begitu, Anda dapat mengambil keanggotaan Ada dengan cepat dan efisien, bahkan ketika daftar pengguna atau grup bertambah menjadi jutaan, atau ketika aturan keamanan Realtime Database menghalangi akses ke beberapa catatan tersebut.

Pendekatan ini, yang membalikkan data dengan mencantumkan ID sebagai kunci dan menetapkan nilai ke true, membuat proses pemeriksaan kunci menjadi semudah membaca /users/$uid/groups/$group_id dan memeriksa apakah nilainya null. Indeks ini lebih cepat dan jauh lebih efisien daripada proses kueri atau memindai data.

Langkah Berikutnya