Bentuk data tidak pernah selesai: bulan depan produk butuh field rating, kuartal depan rating jadi wajib, dan tahun depan muncul format document versi kedua. Database relasional menjawab dengan ALTER TABLE dan migrasi besar-besaran; MongoDB memberi kita peralatan yang lebih halus — field opsional, updateMany, collMod, validationLevel, dan validationAction. Chapter penutup ini merangkai semuanya menjadi strategi evolusi schema yang aman.
Tiga Langkah Evolusi yang Aman #
Polanya selalu sama untuk setiap perubahan bentuk:
- Tambahkan sebagai opsional — daftarkan field baru di
propertiestanpa memasukkannya kerequired. Document lama tetap valid, document baru boleh mengisinya. - Isi nilai default untuk data lama — backfill lewat
updateManydengan filter$exists: false, dipecah menjadi bila data besar. - Perketat menjadi wajib — begitu semua document sudah punya field tersebut, pindahkan ke
requiredlewatcollMod.
Selama proses itu, validationLevel: "moderate" menjaga data lama yang belum dibereskan tetap bisa diedit, dan validationAction: "warn" memberi kita masa pengamatan sebelum penolakan dinyalakan.
Studi Kasus: Menambahkan rating #
Kita mulai dari collection warisan tanpa validator — tiga produk lama tanpa rating:
use latihan_validasi
db.produk.drop()
db.createCollection("produk")
db.produk.insertMany([
{ nama: "Kopi Gayo 250g", harga: 85000, kategori: "Minuman" },
{ nama: "Teh Melati 100g", harga: 12000, kategori: "Minuman" },
{ nama: "Gula Pasir 1kg", harga: 15000, kategori: "Makanan" }
])Output yang Dihasilkan:
switched to db latihan_validasi
true
{ ok: 1 }
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad39225ac74d38a5a53c8b8'),
'1': ObjectId('6ad39225ac74d38a5a53c8b9'),
'2': ObjectId('6ad39225ac74d38a5a53c8ba')
}
}Langkah pertama: pasang validator dengan rating opsional (angka 0–5) plus field penanda schema_version yang juga opsional. Rating sengaja memakai "number" alih-alih "double" agar backfill nilai bulat 0 di langkah kedua tidak tersandung aturan tipe — seperti yang kita lihat di Chapter Tipe BSON dalam Validasi, literal bernilai bulat tersimpan sebagai int:
db.runCommand({ collMod: "produk", validator: { $jsonSchema: {
bsonType: "object",
required: ["nama", "harga"],
properties: {
nama: { bsonType: "string" },
harga: { bsonType: "number", minimum: 0 },
kategori: { enum: ["Minuman", "Makanan", "Alat Tulis"] },
rating: { bsonType: "number", minimum: 0, maximum: 5 },
schema_version: { bsonType: "int" }
}
} } }).ok
db.produk.insertOne({ nama: "Susu UHT 1L", harga: 19500, kategori: "Minuman", rating: 4.5, schema_version: 1 })Output yang Dihasilkan:
1
{
acknowledged: true,
insertedId: ObjectId('6ad39225ac74d38a5a53c8bb')
}Tiga document lama tidak tersentuh, dan document baru yang mengisi rating langsung diterima. Langkah kedua: backfill nilai default 0.0 untuk seluruh document yang belum punya rating, lalu — karena semuanya kini terisi — naikkan rating menjadi wajib:
db.produk.updateMany({ rating: { $exists: false } }, { $set: { rating: 0.0 } })
db.runCommand({ collMod: "produk", validator: { $jsonSchema: {
bsonType: "object",
required: ["nama", "harga", "rating"],
properties: {
nama: { bsonType: "string" },
harga: { bsonType: "number", minimum: 0 },
kategori: { enum: ["Minuman", "Makanan", "Alat Tulis"] },
rating: { bsonType: "number", minimum: 0, maximum: 5 },
schema_version: { bsonType: "int" }
}
} } }).ok
db.produk.insertOne({ nama: "Kopi Tubruk 200g", harga: 12000 })
db.produk.find()Output yang Dihasilkan:
{
acknowledged: true,
insertedId: null,
matchedCount: 3,
modifiedCount: 3,
upsertedCount: 0
}
1
Uncaught:
MongoServerError: Document failed validation
Additional information: {
failingDocumentId: ObjectId('6ad39225ac74d38a5a53c8bc'),
details: {
operatorName: '$jsonSchema',
schemaRulesNotSatisfied: [
{
operatorName: 'required',
specifiedAs: { required: [ 'nama', 'harga', 'rating' ] },
missingProperties: [ 'rating' ]
}
]
}
}
[
{
_id: ObjectId('6ad39225ac74d38a5a53c8b8'),
nama: 'Kopi Gayo 250g',
harga: 85000,
kategori: 'Minuman',
rating: 0
},
{
_id: ObjectId('6ad39225ac74d38a5a53c8b9'),
nama: 'Teh Melati 100g',
harga: 12000,
kategori: 'Minuman',
rating: 0
},
{
_id: ObjectId('6ad39225ac74d38a5a53c8ba'),
nama: 'Gula Pasir 1kg',
harga: 15000,
kategori: 'Makanan',
rating: 0
},
{
_id: ObjectId('6ad39225ac74d38a5a53c8bb'),
nama: 'Susu UHT 1L',
harga: 19500,
kategori: 'Minuman',
rating: 4.5,
schema_version: 1
}
]updateMany mengisi tiga document lama (modifiedCount: 3), validator kedua menaikkan rating ke required, dan insert tanpa rating pun ditolak — kontrak baru sudah berlaku penuh tanpa jedah migrasi.
Pola schema_version #
Ketika perubahan bentuk makin sering, tandai setiap document dengan versi schema yang diikutinya. Kode pembaca lalu bisa berperilaku berbeda per versi, dan migrasi bisa dijalankan per kelompok:
db.produk.insertOne({ nama: "Susu Kental Manis", harga: 12500, kategori: "Makanan", rating: 4.2, schema_version: 2, berat: 0.39 })
db.produk.countDocuments({ schema_version: 1 })
db.produk.countDocuments({ schema_version: 2 })Output yang Dihasilkan:
{
acknowledged: true,
insertedId: ObjectId('6ad39225ac74d38a5a53c8bd')
}
1
1Document versi 2 membawa field berat yang belum diatur validator — sah, karena field baru selalu lahir sebagai opsional. Query countDocuments per versi menjadi alat bantu pantau migrasi: berapa document yang sudah pindah, berapa yang menunggu.
Jembatan moderate dan warn #
Dua kenop dari Chapter sebelumnya sering dipakai berpasangan saat perubahan besar diterapkan pada collection yang masih aktif ditulis:
db.runCommand({ collMod: "produk", validationLevel: "moderate", validationAction: "warn" }).ok
db.produk.insertOne({ nama: "Sirup Markisa", hrga: 21000 })
db.produk.countDocuments()
db.runCommand({ collMod: "produk", validationLevel: "strict", validationAction: "error" }).okOutput yang Dihasilkan:
1
{
acknowledged: true,
insertedId: ObjectId('6ad39225ac74d38a5a53c8be')
}
6
1Selama jembatan itu aktif, insert bermasalah diterima dan pelanggarannya tercatat di log mongod (Chapter Validation Actions) — transaksi tidak putus, tim punya waktu memperbaiki penulis data. Baris terakhir menutup jembatan dan mengembalikan penjagaan penuh. Bersihkan database latihan:
db.dropDatabase()Output yang Dihasilkan:
{ ok: 1, dropped: 'latihan_validasi' }Checklist Evolusi Schema #
- Tambahkan field baru sebagai opsional di
properties— jangan langsung kerequired. - Backfill nilai default dengan
updateMany+$exists: false; pecah menjadi batch kecil untuk collection besar. - Naikkan field ke
requiredhanya setelah semua document terisi. - Tandai document dengan
schema_versiondan pantau progres migrasi lewatcountDocumentsper versi. - Pakai
validationLevel: "moderate"selama data lama belum bersih; kembali ke"strict"setelah selesai. - Pakai
validationAction: "warn"untuk masa pengamatan sebelum menolak; kembali ke"error"setelah penulis data diperbaiki. - Ubah validator bertahap — satu perubahan per rilis jauh lebih mudah dianalisis daripada perombakan sekaligus.
Dengan checklist ini, siklus hidup schema berjalan seiring produk: kontrak data mengencang selangkah demi selangkah, tidak pernah memutus aliran transaksi, dan selalu meninggalkan jejak yang bisa diperiksa.