Semua data yang kita kelola sejauh ini hidup dalam satu document yang berdiri sendiri. Aplikasi nyata jarang sesederhana itu: toko buku kita memiliki pelanggan yang membuat pesanan atas buku tertentu, dan ketiganya layak menjadi collection terpisah. References adalah cara menautkannya — menyimpan nilai _id (atau field unik lain) dari satu document ke dalam field document lain. Chapter ini membedah anatomi referensi itu sendiri: pilihan kunci, arah penunjukan, dan konvensi lama DBRef yang tidak lagi disarankan.

Peta materinya:

  • Referensi sebagai nilai yang disalin — dan konsekuensi tidak adanya foreign key.
  • Pilihan kunci: _id versus natural key seperti sku dan noPesanan.
  • DBRef, konvensi lama yang ditinggalkan.
  • Arah referensi untuk relasi one-to-many dan many-to-many.

Referensi Hanyalah Nilai yang Disalin #

MongoDB tidak memiliki foreign key seperti database relasional. Sebuah referensi hanyalah field biasa yang nilainya kita salin dari document lain — biasanya _id milik document itu. Tidak ada mekanisme server yang otomatis mengikuti referensi, dan tidak ada pemeriksaan yang memastikan document yang ditunjuk masih ada. Seluruh tanggung jawab menjaga dan menyelesaikan referensi berada di tangan aplikasi.

Kita siapkan ketiga collection toko buku. Setiap pesanan menyimpan dua referensi: pelangganId menunjuk document pelanggan, bukuId menunjuk document buku:

js
use latihan_relasi
db.pelanggan.drop()
db.buku.drop()
db.pesanan.drop()
db.pelanggan.insertMany([
  { nama: "Budi", email: "[email protected]", kota: "Bandung" },
  { nama: "Siti", email: "[email protected]", kota: "Jakarta" },
  { nama: "Dewi", email: "[email protected]", kota: "Surabaya" }
])
db.buku.insertMany([
  { sku: "BK-001", judul: "Laut Bercerita", penulis: "Leila S. Chudori", harga: 98000 },
  { sku: "BK-002", judul: "Bumi", penulis: "Tere Liye", harga: 89000 },
  { sku: "BK-003", judul: "Filosofi Teras", penulis: "Henry Manampiring", harga: 72000 }
])
db.pesanan.createIndex({ noPesanan: 1 }, { unique: true })
var idBudi = db.pelanggan.findOne({ nama: "Budi" })._id
var idBuku = {}
db.buku.find({}, { sku: 1 }).forEach(b => idBuku[b.sku] = b._id)
db.pesanan.insertMany([
  { noPesanan: "PSN-001", pelangganId: idBudi, bukuId: idBuku["BK-001"], jumlah: 2 },
  { noPesanan: "PSN-002", pelangganId: idBudi, bukuId: idBuku["BK-002"], jumlah: 1 },
  { noPesanan: "PSN-003", pelangganId: db.pelanggan.findOne({ nama: "Siti" })._id, bukuId: idBuku["BK-003"], jumlah: 3 }
])
db.pesanan.find({}, { _id: 0 })

Output yang Dihasilkan:

text
switched to db latihan_relasi
true
true
true
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ad39a172139099756693d50'),
    '1': ObjectId('6ad39a172139099756693d51'),
    '2': ObjectId('6ad39a172139099756693d52')
  }
}
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ad39a172139099756693d53'),
    '1': ObjectId('6ad39a172139099756693d54'),
    '2': ObjectId('6ad39a172139099756693d55')
  }
}
noPesanan_1

{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ad39a172139099756693d56'),
    '1': ObjectId('6ad39a172139099756693d57'),
    '2': ObjectId('6ad39a172139099756693d58')
  }
}
[
  {
    noPesanan: 'PSN-001',
    pelangganId: ObjectId('6ad39a172139099756693d50'),
    bukuId: ObjectId('6ad39a172139099756693d53'),
    jumlah: 2
  },
  {
    noPesanan: 'PSN-002',
    pelangganId: ObjectId('6ad39a172139099756693d50'),
    bukuId: ObjectId('6ad39a172139099756693d54'),
    jumlah: 1
  },
  {
    noPesanan: 'PSN-003',
    pelangganId: ObjectId('6ad39a172139099756693d51'),
    bukuId: ObjectId('6ad39a172139099756693d55'),
    jumlah: 3
  }
]

Mari kita bedah bagian-bagian pentingnya:

  1. var idBudi = ... ._id: nilai _id pelanggan Budi disalin ke variabel sebelum dipakai di insertMany — persis pola yang akan dipakai aplikasi sungguhan.
  2. var idBuku = {} + forEach: peta kecil dari sku ke _id dibangun sekali, sehingga tiga penyisipan pesanan cukup membaca idBuku["BK-001"] tanpa query tambahan.
  3. db.pesanan.createIndex(...): noPesanan adalah kode pesanan buatan manusia; unique index memastikan tidak ada dua pesanan bernomor sama.
  4. db.pesanan.find(...): hasil akhirnya — setiap pesanan memuat pelangganId dan bukuId berupa ObjectId, salinan dari _id document yang ditunjuk.

Perhatikan bahwa pelangganId: ObjectId('...') di hasil tidak punya kekuatan khusus. Bagi MongoDB, field itu sama seperti field angka atau string lainnya — nilainya hanya kebetulan berbentuk ObjectId.

_id atau Natural Key #

Referensi tidak harus memakai _id. Field unik yang bermakna bagi bisnis — disebut natural key — juga bisa dipakai: sku untuk buku, email untuk pelanggan, atau noPesanan untuk pesanan. Keduanya punya kompromi:

Aspek_id (ObjectId)Natural key (sku, noPesanan)
Ketersediaanotomatis ada di setiap documentharus dikelola sendiri + unique index
Keterbacaannilai acak 12-byte, tak bermaknaterbaca manusia, ramah untuk log dan laporan
Stabilitasdijamin tidak pernah berubahbisa berubah mengikuti proses bisnis
Antar environmentberbeda antara dev dan produksinilainya sama di mana pun
Ukurantetap 12 bytebervariasi, string cenderung lebih panjang

_id adalah pilihan paling aman untuk referensi karena selalu ada dan tidak pernah berubah. Natural key menarik justru pada titik di mana referensi harus keluar dari database — misalnya pengiriman barang dari gudang yang sistemnya terpisah dan hanya mengenal nomor pesanan, bukan ObjectId internal kita:

js
db.pengiriman.drop()
db.pengiriman.insertMany([
  { noPesanan: "PSN-001", kurir: "Kilat", status: "dikirim" },
  { noPesanan: "PSN-003", kurir: "Reguler", status: "diproses" }
])
var kirim = db.pengiriman.findOne({ kurir: "Kilat" })
db.pesanan.find({ noPesanan: kirim.noPesanan }, { _id: 0 })

Output yang Dihasilkan:

text
true
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ad39a172139099756693d59'),
    '1': ObjectId('6ad39a172139099756693d5a')
  }
}

[
  {
    noPesanan: 'PSN-001',
    pelangganId: ObjectId('6ad39a172139099756693d50'),
    bukuId: ObjectId('6ad39a172139099756693d53'),
    jumlah: 2
  }
]

Document pengiriman menautkan dirinya ke pesanan lewat noPesanan — tidak ada ObjectId yang tercecer di sistem logistik. Baris db.pesanan.find({ noPesanan: ... }) menyelesaikan referensi itu kembali ke document pesanan aslinya.

DBRef: Konvensi Lama yang Ditinggalkan #

Versi-versi awal MongoDB mengenal DBRef — konvensi yang membungkus referensi dalam subdocument khusus berisi nama collection, nilai _id, dan kadang nama database:

js
// mdtest: skip
// Bentuk DBRef — hanya ilustrasi sejarah, jangan dipakai
{ noPesanan: "PSN-001", pelanggan: { $ref: "pelanggan", $id: ObjectId("..."), $db: "latihan_relasi" } }

DBRef pernah menjanjikan resolusi referensi otomatis oleh driver. Praktiknya, konvensi ini menambah ukuran document, mempersulit query biasa, dan tidak pernah menjadi bagian inti server. Dokumentasi resmi MongoDB saat ini menyatakan manual references sudah memadai untuk hampir semua kasus dan lebih disarankan daripada DBRef. Untuk course ini kita memakai manual references saja — menyimpan _id atau natural key pada field biasa seperti pelangganId di atas.

Arah Referensi #

Untuk relasi one-to-many, referensi diletakkan di sisi “banyak”: banyak pesanan menunjuk satu pelanggan, bukan sebaliknya. Meletakkan array pesananIds pada document pelanggan juga bisa, tetapi array itu tumbuh tanpa batas seiring pesanan baru — menabrak batas 16MB per document dan memperlambat setiap update pada document pelanggan. Penampakan keseluruhan relasi kita:

Diagram di atas terbaca: satu pelanggan bisa memiliki banyak pesanan (||--o{), dan tautannya adalah field pelangganId di sisi pesanan. Relasi many-to-many antara pelanggan dan buku tidak perlu array ganda — collection pesanan itu sendiri berperan sebagai document penghubung (link), tepat seperti tabel perantara pada database relasional.

Rangkuman #

  • Referensi adalah field biasa berisi nilai (_id atau natural key) dari document lain — server tidak mengikuti atau memverifikasinya otomatis.
  • _id paling aman (selalu ada, tidak berubah); natural key seperti sku ramah manusia dan stabil antar environment, tetapi menuntut unique index.
  • DBRef adalah konvensi lama yang sudah tidak disarankan — gunakan manual references.
  • One-to-many: sisi banyak memegang referensi; many-to-many: gunakan document penghubung seperti pesanan.

Referensi hanyalah data yang tersimpan. Menggunakannya untuk membaca kembali document yang ditunjuk adalah pekerjaan dua langkah — pola yang kita praktikkan di Chapter Manual References.