Proyek yang tumbuh besar menuntut keputusan yang tidak terasa pada proyek kecil, yaitu dari mana konfigurasi dibaca, bagaimana kejadian dicatat, dan bagaimana kode dikelompokkan. Ketiga hal itu menentukan apakah penambahan fitur baru berjalan lancar atau justru menyentuh sepuluh berkas sekaligus. Topik ini membahas konfigurasi berlapis dari nilai bawaan hingga variabel lingkungan, strategi logging yang berhierarki, serta tata letak proyek yang memisahkan lapisan inti dari lapisan antarmukanya. Setiap contohnya dijalankan dengan keluaran asli. Setelah membacanya, tiga penopang utama proyek besar dapat disusun sejak hari pertama, bukan menyusul ketika proyek sudah sulit diubah.
Konfigurasi berlapis#
Nilai konfigurasi berbeda antar lingkungan, misalnya mode jalannya aplikasi dan tingkat kehalusan angka pada laporan. Praktik yang lazim menyusun sumber konfigurasi sebagai lapisan yang menimpa satu sama lain, yaitu nilai bawaan di dalam kode, lalu berkas konfigurasi yang ikut proyek, lalu variabel lingkungan pada puncaknya. Urutan ini membuat lingkungan penyebaran (deployment) dapat mengubah perilaku tanpa menyunting kode, sedangkan berkas tetap menjadi keadaan awal yang terdokumentasi.
Contoh berikut memakai berkas aturan.json sebagai lapisan kedua dan variabel lingkungan berawalan APLIKASI_ sebagai lapisan ketiga.
{
"desimal": 3
}"""Membaca konfigurasi berlapis: bawaan, berkas, lalu lingkungan."""
import json
import os
from pathlib import Path
KONFIG = {"mode": "normal", "desimal": 2}
# lapisan berkas menimpa nilai bawaan, boleh sebagian saja
berkas = Path("aturan.json")
if berkas.exists():
KONFIG.update(json.loads(berkas.read_text(encoding="utf-8")))
# lapisan lingkungan menimpa hasil berkas
if os.environ.get("APLIKASI_MODE"):
KONFIG["mode"] = os.environ["APLIKASI_MODE"]
if os.environ.get("APLIKASI_DESIMAL"):
KONFIG["desimal"] = int(os.environ["APLIKASI_DESIMAL"])
print("mode :", KONFIG["mode"])
print("desimal:", KONFIG["desimal"])mode : normal
desimal: 3Keluaran pertama memperlihatkan lapisan berkas bekerja. Nilai desimal menjadi 3 karena aturan.json menimpa nilai bawaan 2, sedangkan mode tetap normal karena berkas itu sengaja hanya berisi satu kunci, sebuah bukti bahwa penimpaan sebagian didukung. Menjalankan ulang dengan variabel lingkungan memindahkan keputusan akhir ke lapisan ketiga.
# menyetel dua variabel lingkungan lalu menjalankan program
APLIKASI_MODE=debug APLIKASI_DESIMAL=1 python3 aplikasi.pymode : debug
desimal: 1Kedua nilai kini mengikuti lingkungan tanpa satu pun perubahan pada kode maupun berkas. Pola berlapis ini juga menjadi satu-satunya pintu masuk bagi kredensial, sebab nilai rahasia tidak pernah ditulis di dalam kode maupun berkas yang ikut repositori.
Strategi logging#
Logging yang teratur berdiri di atas tiga keputusan, yaitu satu logger untuk setiap modul, level yang disetel pada satu tempat, dan format yang seragam. Modul logging menyediakan ketiganya, dan fitur hierarkinya membuat pengaturan berlaku untuk seluruh turunannya. Logger diberi nama bertitik, dan sebuah logger mewarisi pengaturan logger di atasnya, sehingga satu penyetelan pada logger aplikasi langsung mengatur logger aplikasi.database dan logger turunan lainnya.
import logging
logging.basicConfig(
level=logging.INFO,
format="%(levelname)s:%(name)s:%(message)s",
)
log_utama = logging.getLogger("aplikasi")
log_db = logging.getLogger("aplikasi.database")
log_utama.info("aplikasi dimulai")
log_db.warning("koneksi ke basis data lambat")
log_db.debug("rincian query tidak tampil di level INFO")
log_utama.error("gagal memuat berkas pengaturan")INFO:aplikasi:aplikasi dimulai
WARNING:aplikasi.database:koneksi ke basis data lambat
ERROR:aplikasi:gagal memuat berkas pengaturanPesan berlevel DEBUG tidak muncul karena akar hierarki disetel ke INFO, dan cara menaikkan maupun menurunkannya cukup dilakukan pada satu baris basicConfig(). Pemanggilan log memang tidak menampilkan waktu pada contoh ini, padahal bidang waktu (timestamp) adalah salah satu yang paling sering dipakai, sehingga keluaran logging yang khas memuat waktunya di depan. Pesan logging ditulis ke stderr secara bawaan, sedangkan aplikasi produksi menambahkan penulisan ke berkas beserta rotasinya melalui RotatingFileHandler agar catatan tersimpan dan tidak membengkak.
Tata letak proyek pada skala besar#
Tata letak yang tepat membuat arah ketergantungan terbaca dari struktur foldernya. Pada skala besar, pemisahan yang paling berguna adalah antara lapisan inti yang berisi logika dan lapisan antarmuka yang menyajikannya, misalnya melalui command line atau server. Pohon berikut memperlihatkan bentuk yang lazim untuk proyek bernama statistik-nilai, dengan aturan.json diletakkan di akar proyek sejajar dengan pyproject.toml agar perbedaan antar lingkungan mudah ditemukan.
statistik-nilai/
├── pyproject.toml
├── aturan.json
├── src/
│ └── statistiknilai/
│ ├── __init__.py
│ ├── konfigurasi.py
│ ├── inti/
│ │ ├── agregasi.py
│ │ └── laporan.py
│ └── antarmuka/
│ ├── cli.py
│ └── server.py
└── test/
├── test_agregasi.py
└── test_konfigurasi.pyTiga aturan menjaga pohon itu tetap sehat seiring tumbuhnya proyek. Pertama, modul konfigurasi.py menjadi satu-satunya tempat membaca berkas dan lingkungan, sehingga sisa kode hanya melihat nilai yang sudah jadi. Kedua, arah ketergantungan mengalir satu arah, yaitu antarmuka menggunakan inti, sedangkan inti tidak pernah mengimpor antarmuka. Ketiga, folder test meniru struktur paket yang diujinya, sehingga pengujian untuk satu modul mudah ditemukan.
Folder inti dan antarmuka dipisahkan karena siklus perubahannya berbeda, yaitu logika perhitungan berubah mengikuti kebutuhan pengolahan, sedangkan antarmuka berubah mengikuti cara penyajiannya. Dengan tiga aturan itu, penambahan antarmuka baru, misalnya tugas terjadwal di samping command line, tidak perlu menyentuh logika inti sama sekali.
Kesalahan umum#
Kesalahan pertama adalah membaca variabel lingkungan tersebar di banyak modul. Setiap os.environ yang tambal sulam membuat daftar sumber konfigurasi tidak tertulis di mana pun, dan perubahan nama variabel menyeret banyak berkas. Kumpulkan seluruh pembacaan pada satu modul konfigurasi, lalu bagikan hasilnya kepada modul lain sebagai nilai yang sudah jadi.
Kesalahan kedua adalah mencatat kejadian operasional dengan print(). Keluaran print() tidak membawa level, waktu, maupun asal modulnya, dan tidak dapat disaring tanpa menyunting kodenya. Gunakan logger sejak awal, karena perbedaan biayanya hampir nol sedangkan manfaatnya muncul justru pada saat aplikasi bermasalah. Level pesannya pun dapat diubah sementara tanpa menyunting kode, cukup lewat penyetelan pada titik masuk program.
Kesalahan ketiga adalah membalik arah ketergantungan, misalnya modul inti mengimpor modul antarmuka untuk mengambil konfigurasi. Arah yang terbalik membuat logika inti tidak dapat diuji tanpa menyalakan antarmukanya, dan setiap antarmuka baru menarik perubahan ke inti. Pertahankan arah ketergantungan itu dengan menempatkan pembacaan konfigurasi pada modul tersendiri yang boleh dipakai kedua lapisan.
Ringkasan#
- Konfigurasi berlapis menyusun nilai bawaan, berkas
aturan.json, dan variabel lingkungan dari bawah ke atas, dengan penimpaan sebagian yang didukung. - Logging yang sehat memakai satu logger bertitik per modul, level yang disetel pada satu tempat, dan format seragam, lalu dituliskan ke berkas berrotasi pada produksi.
- Tata letak skala besar memisahkan inti dari antarmuka, menaruh konfigurasi pada satu modul, dan meniru struktur paket di dalam folder
test. - Arah ketergantungan mengalir dari antarmuka ke inti, sehingga inti dapat diuji tanpa menyalakan antarmukanya.
Referensi#
- logging — Python Software Foundation — Referensi API modul logging untuk logger, handler, dan formatter.
- Logging HOWTO — Python Software Foundation — Panduan resmi hierarki logger, level, dan penulisan ke berkas.
- json — Python Software Foundation — Modul yang membaca berkas konfigurasi JSON pada contoh.