02. config dan env: bagaimana konfigurasi masuk
Milik tim (kerangka bawaan). File asli: internal/config/config.go dan internal/core/env.go.
Kamu tidak menulisnya, tetapi store dan polling memakai nilai-nilainya.
Kenapa ada
Ketentuan Implementasi no. 6: kredensial dan konfigurasi wajib dibaca dari environment
variable, tidak boleh tertulis di kode atau ter-commit. Environment variable adalah pengaturan
yang diberikan dari luar program saat ia dijalankan. Di proyek ini, docker-compose.yml
mengisinya dari file .env yang tidak masuk git.
Kenapa dibutuhkan
Tanpa ini, kunci rahasia (BMKG_API_KEY, PVMBG_TOKEN, password Mongo) akan tertulis di kode
dan ikut ter-commit. Spek mengurangi nilai untuk itu. Selain itu, nilai seperti interval polling
bisa diubah tanpa mengubah kode.
Bagian 1: Config, semua pengaturan di satu tempat
Dikutip dari internal/config/config.go, baris 11 sampai 31
type Config struct {
ServiceName string
LogLevel string
Port int
// Upstream sources. Each has its own credential; they must never be swapped.
BMKGBaseURL string
BMKGAPIKey string
PVMBGBaseURL string
PVMBGToken string
PollInterval time.Duration
UpstreamTimeout time.Duration
// Canonical Store (owned exclusively by this service)
MongoURI string
MongoDB string
// Message broker
KafkaBootstrapServers string
KafkaHazardTopic string
}Satu struct menampung semua pengaturan. Bagian lain tidak memanggil os.Getenv sendiri-sendiri.
Yang dipakai tugasmu:
| Field | Dipakai oleh | Contoh nilai |
|---|---|---|
BMKGBaseURL, BMKGAPIKey | klien BMKG | http://bmkg-mock:8081, kunci rahasia |
PVMBGBaseURL, PVMBGToken | klien PVMBG | http://pvmbg-mock:8082, token rahasia |
PollInterval | ingest.Run | 3 detik |
UpstreamTimeout | core.HTTPClient | 5 detik |
MongoURI, MongoDB | store.New | alamat Mongo + nama database bnpb |
Nama host seperti bmkg-mock dan mongo bukan alamat internet. Itu nama service di
docker-compose, yang diterjemahkan Docker menjadi alamat container di jaringan internalnya.
Bagian 2: Load, membaca dari environment
Dikutip dari internal/config/config.go, baris 33 sampai 54
func Load() (Config, error) {
var e core.Env
c := Config{
ServiceName: e.String("SERVICE_NAME", "bnpb-aggregator"),
LogLevel: e.String("LOG_LEVEL", "INFO"),
Port: e.Int("PORT", 8000),
BMKGBaseURL: e.String("BMKG_BASE_URL", "http://bmkg-mock:8081"),
BMKGAPIKey: e.Required("BMKG_API_KEY"),
PVMBGBaseURL: e.String("PVMBG_BASE_URL", "http://pvmbg-mock:8082"),
PVMBGToken: e.Required("PVMBG_TOKEN"),
PollInterval: e.Seconds("POLL_INTERVAL_SECONDS", 3*time.Second),
UpstreamTimeout: e.Millis("UPSTREAM_TIMEOUT_MS", 5000*time.Millisecond),
MongoURI: e.Required("MONGO_URI"),
MongoDB: e.String("MONGO_DB", "bnpb"),
KafkaBootstrapServers: e.String("KAFKA_BOOTSTRAP_SERVERS", "kafka:9092"),
KafkaHazardTopic: e.String("KAFKA_HAZARD_TOPIC", "hazard-events"),
}
return c, e.Err()
}e.String("SERVICE_NAME", "bnpb-aggregator"): ambil nilainya, atau pakai nilai bawaan kalau tidak diisi.e.Required("BMKG_API_KEY"): wajib. Tidak ada nilai bawaan, supaya tidak ada rahasia yang tertanam di kode. Kalau kosong, service menolak start.e.Seconds("POLL_INTERVAL_SECONDS", 3*time.Second): membaca angka detik (boleh pecahan) dan mengubahnya menjaditime.Duration, tipe durasi Go yang langsung bisa dipakai untuk ticker dan timeout.- Di akhir,
return c, e.Err(): semua masalah yang terkumpul, ataunilkalau konfigurasi valid.
Bagian 3: Env, membaca sambil mengumpulkan error
Dikutip dari internal/core/env.go, baris 22 sampai 28
func (e *Env) Required(key string) string {
v := os.Getenv(key)
if v == "" {
e.errs = append(e.errs, fmt.Errorf("%s is required", key))
}
return v
}Dikutip dari internal/core/env.go, baris 44 sampai 55
func (e *Env) Seconds(key string, def time.Duration) time.Duration {
v, ok := os.LookupEnv(key)
if !ok || v == "" {
return def
}
f, err := strconv.ParseFloat(v, 64)
if err != nil || f <= 0 {
e.errs = append(e.errs, fmt.Errorf("%s must be a positive number of seconds, got %q", key, v))
return def
}
return time.Duration(f * float64(time.Second))
}Dikutip dari internal/core/env.go, baris 74 sampai 74
func (e *Env) Err() error { return errors.Join(e.errs...) }Yang membuat Env berguna: setiap method mencatat error ke daftar e.errs alih-alih langsung
berhenti. Err() menggabungkan semuanya (errors.Join). Hasilnya, kalau tiga variabel wajib
kosong, kamu mendapat satu pesan yang menyebut ketiganya, bukan harus menjalankan ulang tiga
kali untuk menemukannya satu per satu. (Pelajaran 2.)
Kalau kamu menambah variabel baru
Aturan repo: tambahkan di tiga tempat.
internal/config/config.go(dibaca),.env.example(didokumentasikan dengan nilai contoh),docker-compose.yml(diteruskan ke container service itu).
Kalau salah satu terlewat, service gagal start di Docker, atau reviewer tidak tahu variabel itu ada.
Cara menjelaskan dalam 30 detik
Semua pengaturan dan rahasia dibaca dari environment variable lewat
config.Load, tidak pernah tertulis di kode. Variabel wajib yang kosong dilaporkan sekaligus lalu service menolak start. Isinya diberikandocker-compose.ymldari file.envyang tidak di-commit.
Latihan
- Variabel apa yang akan kamu ubah untuk membuat polling tiap 5 detik? Apakah perlu mengubah kode?
- Kenapa
BMKG_API_KEYmemakaiRequireddan bukanStringdengan nilai bawaan? - Apa gunanya
Envmengumpulkan error, dibanding berhenti di error pertama?