Schema yang dirancang pada topik pemodelan domain kini dipakai oleh aplikasi yang menulis dan membacanya. Topik ini membangun aplikasi CRUD lengkap untuk basis data pencatatan dalam satu program Python bernama aplikasi.py, mulai dari penyiapan koleksi ber-validator, operasi create, read, update, dan delete, agregasi ringkasan bulanan, sampai pembuatan indeks. Driver yang dipakai adalah PyMongo karena sudah terpasang secara global pada lingkungan latihan, sehingga program dapat dijalankan tanpa penyiapan tambahan. Keluarannya deterministik, yaitu sama persis pada setiap eksekusi, sehingga menjadi pembanding yang stabil ketika program diubah.
Struktur program dan penyiapan koleksi#
Program aplikasi.py mengurus seluruh siklusnya sendiri dan dijalankan dari folder topik. Koneksi dibuat melalui MongoClient dengan alamat server lokal, lalu basis data dan kedua koleksi ditunjuk lewat kurung siku. Berbeda dari driver Node.js, PyMongo tidak memerlukan pemanggilan connect() eksplisit karena koneksi baru dibangun pada saat operasi pertama dieksekusi. Langkah penyiapan menjatuhkan kedua koleksi dengan drop() dan membuatnya ulang melalui create_collection dengan validator yang sama dengan desain schema. Dengan demikian schema bukan sekadar gambaran, melainkan aturan yang terpasang di server setiap kali program berjalan, dan keluaran program tidak pernah tercemar sisa eksekusi sebelumnya.
# Aplikasi pencatatan pengeluaran pribadi dengan PyMongo
from datetime import datetime
from bson.decimal128 import Decimal128
from pymongo import MongoClient
klien = MongoClient("mongodb://127.0.0.1:27017")
db = klien["pencatatan"]
kategori = db["kategori"]
pengeluaran = db["pengeluaran"]
# PENYIAPAN: koleksi dijatuhkan lalu dibuat ulang dengan validator desain
skemaKategori = {
"$jsonSchema": {
"bsonType": "object",
"required": ["nama", "batasBulanan"],
"properties": {
"nama": {"bsonType": "string"},
"batasBulanan": {"bsonType": "decimal"},
},
}
}
skemaPengeluaran = {
"$jsonSchema": {
"bsonType": "object",
"required": ["tanggal", "kategori", "jumlah"],
"properties": {
"tanggal": {"bsonType": "date"},
"kategori": {"bsonType": "string"},
"jumlah": {"bsonType": "decimal"},
"catatan": {"bsonType": "string"},
},
}
}
kategori.drop()
pengeluaran.drop()
db.create_collection("kategori", validator=skemaKategori)
db.create_collection("pengeluaran", validator=skemaPengeluaran)
print("Koleksi dibuat ulang: kategori, pengeluaran")
# CREATE: daftar kategori dan pengeluaran harian yang menjadi data awal
kategori.insert_many([
{"nama": "makan", "batasBulanan": Decimal128("900000")},
{"nama": "transportasi", "batasBulanan": Decimal128("500000")},
{"nama": "belanja", "batasBulanan": Decimal128("600000")},
{"nama": "hiburan", "batasBulanan": Decimal128("120000")},
{"nama": "kesehatan", "batasBulanan": Decimal128("400000")},
])
print("Kategori terdaftar:", kategori.count_documents({}))
seed = pengeluaran.insert_many([
{"tanggal": datetime(2026, 6, 1), "kategori": "makan", "jumlah": Decimal128("32000"), "catatan": "sarapan dan makan siang"},
{"tanggal": datetime(2026, 6, 1), "kategori": "transportasi", "jumlah": Decimal128("14000"), "catatan": "pulang-pergi kerja"},
{"tanggal": datetime(2026, 6, 2), "kategori": "makan", "jumlah": Decimal128("27000"), "catatan": "makan malam"},
{"tanggal": datetime(2026, 6, 2), "kategori": "hiburan", "jumlah": Decimal128("60000"), "catatan": "tiket bioskop"},
{"tanggal": datetime(2026, 6, 3), "kategori": "belanja", "jumlah": Decimal128("125000"), "catatan": "deterjen dan sabun"},
{"tanggal": datetime(2026, 6, 3), "kategori": "kesehatan", "jumlah": Decimal128("90000"), "catatan": "obat batuk"},
])
print("Dokumen pengeluaran baru:", len(seed.inserted_ids))
pengeluaran.insert_one({"tanggal": datetime(2026, 6, 3), "kategori": "makan", "jumlah": Decimal128("35000"), "catatan": "bahan masak"})
# READ: satu dokumen dan satu rentang tanggal
pertama = pengeluaran.find_one({"kategori": "makan"}, sort=[("tanggal", 1)])
print("Catatan makan pertama:", pertama["catatan"])
print("Pengeluaran 2 sampai 4 Juni:")
for d in pengeluaran.find(
{"tanggal": {"$gte": datetime(2026, 6, 2), "$lt": datetime(2026, 6, 4)}},
{"_id": 0, "tanggal": 1, "kategori": 1, "jumlah": 1},
).sort([("tanggal", -1), ("kategori", 1)]):
print(d["tanggal"].strftime("%Y-%m-%d"), f"{d['kategori']:<13}", str(d["jumlah"]))
# UPDATE: membetulkan jumlah tiket bioskop
ubah = pengeluaran.update_one({"catatan": "tiket bioskop"}, {"$set": {"jumlah": Decimal128("55000")}})
print("Dokumen diubah:", ubah.modified_count)
# DELETE: menghapus satu pengeluaran yang salah catat
hapus = pengeluaran.delete_one({"kategori": "kesehatan"})
print("Dokumen dihapus:", hapus.deleted_count)
print("Sisa dokumen:", pengeluaran.count_documents({}))
# AGREGASI: ringkasan per kategori untuk bulan Juni 2026
pipeline = [
{"$match": {"tanggal": {"$gte": datetime(2026, 6, 1), "$lt": datetime(2026, 7, 1)}}},
{"$group": {"_id": "$kategori", "total": {"$sum": "$jumlah"}}},
{"$sort": {"_id": 1}},
]
print("Ringkasan Juni 2026 per kategori:")
for baris in pengeluaran.aggregate(pipeline):
print(f"{baris['_id']:<13}", str(baris["total"]))
# INDEKS: dua indeks untuk pola baca aplikasi
pengeluaran.create_index([("tanggal", -1)])
pengeluaran.create_index([("kategori", 1), ("tanggal", -1)])
for nama in sorted(idx["name"] for idx in pengeluaran.list_indexes()):
print("Indeks pengeluaran:", nama)
klien.close()Koleksi dibuat ulang: kategori, pengeluaran
Kategori terdaftar: 5
Dokumen pengeluaran baru: 6
Catatan makan pertama: sarapan dan makan siang
Pengeluaran 2 sampai 4 Juni:
2026-06-03 belanja 125000
2026-06-03 kesehatan 90000
2026-06-03 makan 35000
2026-06-02 hiburan 60000
2026-06-02 makan 27000
Dokumen diubah: 1
Dokumen dihapus: 1
Sisa dokumen: 6
Ringkasan Juni 2026 per kategori:
belanja 125000
hiburan 55000
makan 94000
transportasi 14000
Indeks pengeluaran: _id_
Indeks pengeluaran: kategori_1_tanggal_-1
Indeks pengeluaran: tanggal_-1Keluaran program menyusuri urutan kerjanya. Lima baris pertama berasal dari penyiapan dan penyisipan data awal. Bagian tengahnya memuat hasil pembacaan, pembaruan, dan penghapusan, sedangkan bagian akhirnya menampilkan ringkasan agregasi beserta daftar indeks. Daftar ringkasan per kategori hanya memuat empat baris karena kategori kesehatan sudah dihapus sebelum agregasi berjalan. Setiap angka pada keluaran berasal dari data yang ditanam program itu sendiri, sehingga pembacaannya dapat diverifikasi baris demi baris.
Operasi CRUD yang dirangkai#
Bagian create menanam data melalui insert_many untuk dokumen awal dan insert_one untuk transaksi tambahan. Banyaknya dokumen yang berhasil ditanam dapat dibaca dari atribut inserted_ids pada hasil insert_many, sedangkan hasil insert_one tidak dipakai karena transaksinya langsung terlihat pada pembacaan berikutnya. Nilai jumlah wajib dibungkus Decimal128 karena validator menuntut tipe decimal, sehingga bilangan Python biasa akan ditolak server.
Bagian read memperlihatkan dua bentuk pembacaan. find_one dengan parameter sort=[("tanggal", 1)] mengambil transaksi makan paling awal, sedangkan find pada rentang 2 sampai 4 Juni memakai proyeksi yang menyertakan tanggal, kategori, dan jumlah. Pengurutannya majemuk, yaitu tanggal menurun lalu kategori menaik, sehingga urutan tampilan tetap terdeterminasi walaupun beberapa transaksi berbagi hari yang sama. Pemformatan kolom pada keluaran memakai strftime untuk tanggal dan str untuk Decimal128.
Bagian update dan delete menutup siklus CRUD. update_one membetulkan jumlah tiket bioskop dari 60000 menjadi 55000 melalui $set, sedangkan delete_one menghapus transaksi kesehatan yang salah catat. Atribut modified_count dan deleted_count melaporkan banyaknya dokumen yang benar-benar tersentuh, dan keduanya bernilai 1 pada keluaran program.
Ringkasan bulanan dengan agregasi#
Ringkasan bulanan dihitung dengan pipeline tiga tahap yang mengikuti pola agregasi pada modul pipeline. $match menyaring rentang tanggal Juni 2026, $group menjumlahkan jumlah per kategori dengan $sum, dan $sort menata hasilnya menurut kategori. Akumulator $sum menjumlahkan nilai Decimal128 dan mengembalikan Decimal128, sehingga ringkasannya eksak sebagaimana desain menuntut. Tahap $sort di akhir dipertahankan karena urutan keluaran $group tidak dijamin oleh server, sebagaimana pola yang diajarkan pada modul pipeline.
Pencetakan hasilnya memakai dua penyesuaian kecil. Nilai total dikonversi dengan str agar keluarannya berupa angka tanpa pembungkus tipe, sedangkan nama kategori diratakan kiri ke lebar 13 karakter melalui format :<13 supaya kolomnya sejajar. Pipeline ini juga sama bentuknya dengan uji pertanyaan domain pada pemodelan domain, sehingga strukturnya dapat dibandingkan baris demi baris.
Hasil ringkasan mencerminkan seluruh operasi yang berjalan lebih dahulu. Nilai hiburan adalah 55000 karena pembaruan tiket bioskop sudah dieksekusi, sedangkan kategori kesehatan hilang dari daftar karena dokumennya sudah dihapus. Total makan 94000 terdiri atas tiga transaksi, yaitu 32000, 27000, dan 35000 dari transaksi tambahan. Urutan langkah ini penting, karena ringkasan yang dihitung sebelum pembaruan dan penghapusan akan menampilkan nilai lama.
Indeks dari aplikasi#
Dua indeks dibuat dari kode aplikasi melalui create_index tanpa perintah manual di mongosh. Indeks pertama adalah tanggal menurun untuk pola pembacaan pengeluaran terbaru pada rentang tanggal, sedangkan indeks kedua adalah komposit kategori menaik dan tanggal menurun untuk pola pembacaan per kategori. Nama keduanya, yaitu tanggal_-1 dan kategori_1_tanggal_-1, muncul pada daftar list_indexes() yang dicetak terurut agar keluarannya tetap stabil.
Pemilihan indeks mengikuti pola baca aplikasi, bukan daftar field yang ada. Indeks komposit menempatkan kategori lebih dahulu karena pola baca per kategori menyaring kategori dengan kesetaraan sebelum menyaring tanggal. Pemakaian indeks oleh server belum terbukti dari pembuatan saja, sehingga verifikasinya dilakukan dengan explain() pada penyisiran performa.
Program dijalankan dari folder topik dengan perintah berikut, dan keluarannya identik dengan keluaran program di atas karena programnya sama.
# menjalankan aplikasi pencatatan dari folder topik
python3 aplikasi.pyKoleksi dibuat ulang: kategori, pengeluaran
Kategori terdaftar: 5
Dokumen pengeluaran baru: 6
Catatan makan pertama: sarapan dan makan siang
Pengeluaran 2 sampai 4 Juni:
2026-06-03 belanja 125000
2026-06-03 kesehatan 90000
2026-06-03 makan 35000
2026-06-02 hiburan 60000
2026-06-02 makan 27000
Dokumen diubah: 1
Dokumen dihapus: 1
Sisa dokumen: 6
Ringkasan Juni 2026 per kategori:
belanja 125000
hiburan 55000
makan 94000
transportasi 14000
Indeks pengeluaran: _id_
Indeks pengeluaran: kategori_1_tanggal_-1
Indeks pengeluaran: tanggal_-1Kesalahan umum#
Kesalahan pertama adalah memanggil create_collection pada koleksi yang masih ada. PyMongo mengangkat CollectionInvalid karena koleksi tersebut sudah terdaftar, sehingga program berhenti sebelum satu dokumen pun ditulis. Perbaikannya adalah menjatuhkan koleksi dengan drop() sebelum membuatnya ulang, sebagaimana dilakukan di awal program.
Kesalahan kedua adalah menuliskan jumlah sebagai bilangan bulat Python biasa. Validator menuntut bsonType decimal, sedangkan bilangan Python dikodekan sebagai int32 atau int64, sehingga server menolak tulisan itu dengan WriteError berkode 121, yaitu kegagalan validasi dokumen. Perbaikannya adalah membungkus nilai dengan Decimal128 pada setiap dokumen yang menulis jumlah.
Kesalahan ketiga adalah menyimpan tanggal sebagai string agar tampak sederhana. Penyaringan rentang pada string ISO memang tampak bekerja karena urutan hurufnya sejalan dengan urutan waktu, tetapi tanggal tidak lagi diperlakukan sebagai waktu. Akibatnya, agregasi berbasis bulan dan indeks bertipe date tidak dapat dipakai dengan benar. String juga tidak membedakan zona waktu, sedangkan tipe date BSON menyimpan milidetik dalam satu acuan UTC. Perbaikannya adalah memakai datetime Python yang dikodekan sebagai tipe date BSON.
Ringkasan#
- Program
aplikasi.pymerangkai siklus penuh, yaitu penyiapan koleksi ber-validator, CRUD, agregasi, dan pembuatan indeks, dalam satu eksekusi yang deterministik. create_collectionmemasang validator desain, sehingga aturan schema tetap hidup di dalam aplikasi.- Pipeline
$match,$group, dan$sortmenghasilkan ringkasan per kategori yang mencerminkan seluruh operasi tulis sebelumnya. - Dua indeks, yaitu
tanggal_-1dankategori_1_tanggal_-1, dibuat dari kode dan rencananya diverifikasi pada penyisiran performa.
Referensi#
- Get Started with PyMongo — MongoDB Documentation — Pemasangan PyMongo dan program pertama dari Python.
- CRUD Operations — PyMongo Driver — Panduan operasi create, read, update, dan delete pada PyMongo.
- Aggregation Operations — PyMongo Driver — Pemakaian pipeline agregasi dari PyMongo.
- Indexes — PyMongo Driver — Pembuatan dan pengelolaan indeks dari kode aplikasi.
- db.collection.createIndex() — MongoDB Manual — Spesifikasi pembuatan indeks yang dipanggil driver.