unisbadri.com » Python Java Golang Typescript Kotlin Ruby Rust Dart PHP

Yaml #

YAML (YAML Ain’t Markup Language) adalah format serialisasi data yang lebih mudah dibaca manusia dibanding JSON — tidak ada tanda kurung kurawal, tidak ada koma, indentasi menentukan struktur. YAML sangat populer untuk file konfigurasi: Kubernetes manifests, GitHub Actions, Docker Compose, Ansible playbooks — semuanya menggunakan YAML. Go tidak menyertakan parser YAML di standard library, tapi gopkg.in/yaml.v3 adalah library yang matang dan menjadi standar de-facto di ekosistem Go.

Instalasi #

go get gopkg.in/yaml.v3

Sintaks YAML — Ringkasan Cepat #

Sebelum masuk ke Go, pahami sintaks YAML yang akan sering ditemui:

# Komentar dengan #

# String — tidak perlu quote kecuali ada karakter spesial
nama: Budi Santoso
kota: "Jakarta: Pusat"    # quote karena ada titik dua

# Angka
umur: 28
harga: 15000000
pi: 3.14159

# Boolean (case-insensitive)
aktif: true
terverifikasi: false

# Null
email: null
catatan: ~        # alternatif null

# Array (block style — satu elemen per baris)
skills:
  - Go
  - Python
  - Docker

# Array (flow style — satu baris)
tags: [backend, api, microservice]

# Object nested
alamat:
  jalan: Jl. Merdeka No. 1
  kota: Jakarta
  kode_pos: "10110"     # string, bukan integer

# Array of objects
produk:
  - id: 1
    nama: Laptop
    harga: 15000000
  - id: 2
    nama: Mouse
    harga: 350000

# Multi-line string
deskripsi: |
  Baris pertama.
  Baris kedua.
  Baris ketiga dengan newline di akhir setiap baris.  

ringkasan: >
  Semua baris ini akan digabung
  menjadi satu baris panjang
  dengan spasi sebagai pemisah.  

# Anchor (&) dan alias (*) — reuse nilai
defaults: &defaults
  timeout: 30
  retry: 3

production:
  <<: *defaults    # merge defaults
  host: prod.example.com

Marshal — Go ke YAML #

import "gopkg.in/yaml.v3"

type Config struct {
    Server   ServerConfig   `yaml:"server"`
    Database DatabaseConfig `yaml:"database"`
    Features []string       `yaml:"features"`
}

type ServerConfig struct {
    Host    string `yaml:"host"`
    Port    int    `yaml:"port"`
    Debug   bool   `yaml:"debug"`
}

type DatabaseConfig struct {
    URL      string `yaml:"url"`
    MaxConns int    `yaml:"max_connections"`
    Timeout  int    `yaml:"timeout_seconds"`
}

cfg := Config{
    Server: ServerConfig{
        Host:  "0.0.0.0",
        Port:  8080,
        Debug: false,
    },
    Database: DatabaseConfig{
        URL:      "postgres://localhost/myapp",
        MaxConns: 25,
        Timeout:  30,
    },
    Features: []string{"auth", "payments", "notifications"},
}

data, err := yaml.Marshal(cfg)
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(data))

Output:

server:
    host: 0.0.0.0
    port: 8080
    debug: false
database:
    url: postgres://localhost/myapp
    max_connections: 25
    timeout_seconds: 30
features:
    - auth
    - payments
    - notifications

Unmarshal — YAML ke Go #

yamlContent := `
server:
  host: localhost
  port: 9090
  debug: true

database:
  url: postgres://localhost/testdb
  max_connections: 10
  timeout_seconds: 5

features:
  - auth
  - payments
`

var cfg Config
if err := yaml.Unmarshal([]byte(yamlContent), &cfg); err != nil {
    log.Fatal("gagal parse YAML:", err)
}

fmt.Printf("Server: %s:%d (debug=%v)\n",
    cfg.Server.Host, cfg.Server.Port, cfg.Server.Debug)
fmt.Printf("Database: %s\n", cfg.Database.URL)
fmt.Printf("Features: %v\n", cfg.Features)

Membaca dari File #

func loadConfig(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("baca file config: %w", err)
    }

    var cfg Config
    if err := yaml.Unmarshal(data, &cfg); err != nil {
        return nil, fmt.Errorf("parse YAML: %w", err)
    }

    return &cfg, nil
}

Struct Tags YAML #

Tag YAML bekerja mirip dengan JSON tapi dengan sintaks yaml:"...":

type AppConfig struct {
    // Rename field
    AppName string `yaml:"app_name"`

    // omitempty: skip jika zero value
    DebugMode bool   `yaml:"debug,omitempty"`
    LogLevel  string `yaml:"log_level,omitempty"`

    // "-": selalu skip
    InternalSecret string `yaml:"-"`

    // inline: "flatten" field dari struct lain ke level ini
    CommonConfig `yaml:",inline"`

    // flow: gunakan flow style (satu baris) untuk marshal
    Tags []string `yaml:"tags,flow"`
}

type CommonConfig struct {
    Timeout int `yaml:"timeout"`
    Retry   int `yaml:"retry"`
}

// Contoh dengan inline:
// timeout: 30     ← dari CommonConfig (di-inline, bukan nested)
// retry: 3
// tags: [a, b, c] ← flow style

Custom Marshaler dan Unmarshaler #

Implementasikan interface yaml.Marshaler dan yaml.Unmarshaler untuk kontrol penuh:

// Duration yang serializable ke format "30s", "5m", "1h"
type Duration struct {
    time.Duration
}

func (d Duration) MarshalYAML() (interface{}, error) {
    return d.String(), nil  // "1m30s", "5s", dll
}

func (d *Duration) UnmarshalYAML(value *yaml.Node) error {
    var s string
    if err := value.Decode(&s); err != nil {
        return err
    }
    dur, err := time.ParseDuration(s)
    if err != nil {
        return fmt.Errorf("format durasi tidak valid %q: %w", s, err)
    }
    d.Duration = dur
    return nil
}

// Penggunaan
type ServerConfig struct {
    Host            string   `yaml:"host"`
    ReadTimeout     Duration `yaml:"read_timeout"`
    WriteTimeout    Duration `yaml:"write_timeout"`
    ShutdownTimeout Duration `yaml:"shutdown_timeout"`
}

// YAML input
yamlStr := `
host: localhost
read_timeout: 5s
write_timeout: 10s
shutdown_timeout: 30s
`

var srv ServerConfig
yaml.Unmarshal([]byte(yamlStr), &srv)
fmt.Println(srv.ReadTimeout.Duration)  // 5s sebagai time.Duration

Multi-Document YAML #

YAML mendukung beberapa dokumen dalam satu file, dipisahkan dengan ---:

// Kubernetes-style multi-document
multiDoc := `
---
kind: Service
name: api-server
port: 8080
---
kind: Database
name: postgres
port: 5432
---
kind: Cache
name: redis
port: 6379
`

decoder := yaml.NewDecoder(strings.NewReader(multiDoc))

for {
    var doc map[string]interface{}
    if err := decoder.Decode(&doc); err != nil {
        if err == io.EOF {
            break
        }
        log.Fatal(err)
    }
    fmt.Printf("Kind: %s, Name: %s\n", doc["kind"], doc["name"])
}

YAML vs JSON — Kapan Pakai Mana #

GUNAKAN YAML untuk:
  ✓ File konfigurasi yang diedit manusia (config.yaml, docker-compose.yml)
  ✓ Infrastructure as Code (Kubernetes, Ansible, Terraform)
  ✓ CI/CD pipeline definition (GitHub Actions, GitLab CI)
  ✓ Dokumentasi API (OpenAPI/Swagger)
  ✓ Template dengan komentar dan anchor/alias

GUNAKAN JSON untuk:
  ✓ API request/response (JSON lebih universal)
  ✓ Penyimpanan data (database, cache)
  ✓ Komunikasi antar service (JSON lebih cepat di-parse)
  ✓ Package.json, go.mod-style config (tidak butuh komentar)
  ✓ Ketika struktur sangat dalam — YAML indent bisa membingungkan

Perbandingan fitur:
  YAML                    JSON
  Komentar (#)    ✓       Tidak ada
  Anchor/alias    ✓       Tidak ada
  Multi-line str  ✓       Terbatas
  Type coercion   ✓       Tidak ada
  Keterbacaan     ✓       Lebih verbose
  Parsing speed   Lebih lambat   Lebih cepat
  Keamanan        Lebih riskan   Lebih aman
YAML bisa berbahaya jika parsing file dari sumber tidak dipercaya. YAML spec lama mendukung tipe eksekusi (!!python/object) dan fitur berbahaya lain. gopkg.in/yaml.v3 sudah aman untuk file konfigurasi biasa, tapi jangan gunakan yaml.Unmarshal ke interface{} untuk input dari pengguna tanpa validasi.

Contoh Program Lengkap — Config Loader #

Program berikut membangun config loader yang type-safe dengan validasi, nilai default, dan dukungan environment variable:

package main

import (
    "fmt"
    "log"
    "os"
    "strings"
    "time"

    "gopkg.in/yaml.v3"
)

// ── Config Types ──────────────────────────────────────────────

type Duration struct{ time.Duration }

func (d Duration) MarshalYAML() (interface{}, error) { return d.String(), nil }

func (d *Duration) UnmarshalYAML(v *yaml.Node) error {
    var s string
    if err := v.Decode(&s); err != nil {
        return err
    }
    dur, err := time.ParseDuration(s)
    if err != nil {
        return fmt.Errorf("durasi tidak valid %q: %w", s, err)
    }
    d.Duration = dur
    return nil
}

type AppConfig struct {
    App      AppSection      `yaml:"app"`
    HTTP     HTTPSection     `yaml:"http"`
    Database DatabaseSection `yaml:"database"`
    Redis    RedisSection    `yaml:"redis"`
    Log      LogSection      `yaml:"log"`
}

type AppSection struct {
    Name    string `yaml:"name"`
    Version string `yaml:"version"`
    Env     string `yaml:"env"`     // development, staging, production
}

type HTTPSection struct {
    Host            string   `yaml:"host"`
    Port            int      `yaml:"port"`
    ReadTimeout     Duration `yaml:"read_timeout"`
    WriteTimeout    Duration `yaml:"write_timeout"`
    ShutdownTimeout Duration `yaml:"shutdown_timeout"`
    CORS            CORSConfig `yaml:"cors"`
}

type CORSConfig struct {
    Enabled bool     `yaml:"enabled"`
    Origins []string `yaml:"origins"`
}

type DatabaseSection struct {
    Host         string   `yaml:"host"`
    Port         int      `yaml:"port"`
    Name         string   `yaml:"name"`
    User         string   `yaml:"user"`
    Password     string   `yaml:"password"`
    MaxOpenConns int      `yaml:"max_open_conns"`
    MaxIdleConns int      `yaml:"max_idle_conns"`
    ConnLifetime Duration `yaml:"conn_lifetime"`
    SSLMode      string   `yaml:"ssl_mode"`
}

func (d DatabaseSection) DSN() string {
    return fmt.Sprintf("host=%s port=%d dbname=%s user=%s password=%s sslmode=%s",
        d.Host, d.Port, d.Name, d.User, d.Password, d.SSLMode)
}

type RedisSection struct {
    Host     string   `yaml:"host"`
    Port     int      `yaml:"port"`
    Password string   `yaml:"password"`
    DB       int      `yaml:"db"`
    Timeout  Duration `yaml:"timeout"`
}

func (r RedisSection) Addr() string {
    return fmt.Sprintf("%s:%d", r.Host, r.Port)
}

type LogSection struct {
    Level  string `yaml:"level"`   // debug, info, warn, error
    Format string `yaml:"format"`  // json, text
    Output string `yaml:"output"`  // stdout, stderr, file path
}

// ── Default Config ────────────────────────────────────────────

var defaultConfig = AppConfig{
    App: AppSection{
        Name:    "myapp",
        Version: "0.0.1",
        Env:     "development",
    },
    HTTP: HTTPSection{
        Host:            "0.0.0.0",
        Port:            8080,
        ReadTimeout:     Duration{5 * time.Second},
        WriteTimeout:    Duration{10 * time.Second},
        ShutdownTimeout: Duration{30 * time.Second},
        CORS: CORSConfig{
            Enabled: true,
            Origins: []string{"*"},
        },
    },
    Database: DatabaseSection{
        Host:         "localhost",
        Port:         5432,
        MaxOpenConns: 25,
        MaxIdleConns: 5,
        ConnLifetime: Duration{30 * time.Minute},
        SSLMode:      "disable",
    },
    Redis: RedisSection{
        Host:    "localhost",
        Port:    6379,
        DB:      0,
        Timeout: Duration{3 * time.Second},
    },
    Log: LogSection{
        Level:  "info",
        Format: "json",
        Output: "stdout",
    },
}

// ── Loader ────────────────────────────────────────────────────

func LoadConfig(path string) (*AppConfig, error) {
    cfg := defaultConfig  // mulai dari default

    // Load dari file jika ada
    if path != "" {
        data, err := os.ReadFile(path)
        if err != nil {
            if !os.IsNotExist(err) {
                return nil, fmt.Errorf("baca config file: %w", err)
            }
            log.Printf("Config file %q tidak ditemukan, menggunakan default", path)
        } else {
            if err := yaml.Unmarshal(data, &cfg); err != nil {
                return nil, fmt.Errorf("parse config YAML: %w", err)
            }
        }
    }

    // Override dari environment variables
    overrideFromEnv(&cfg)

    // Validasi
    if err := validateConfig(&cfg); err != nil {
        return nil, fmt.Errorf("config tidak valid: %w", err)
    }

    return &cfg, nil
}

func overrideFromEnv(cfg *AppConfig) {
    envMap := map[string]*string{
        "APP_ENV":       &cfg.App.Env,
        "DB_HOST":       &cfg.Database.Host,
        "DB_NAME":       &cfg.Database.Name,
        "DB_USER":       &cfg.Database.User,
        "DB_PASSWORD":   &cfg.Database.Password,
        "REDIS_HOST":    &cfg.Redis.Host,
        "REDIS_PASSWORD": &cfg.Redis.Password,
    }
    for envKey, target := range envMap {
        if val := os.Getenv(envKey); val != "" {
            *target = val
        }
    }
}

func validateConfig(cfg *AppConfig) error {
    var errs []string

    if cfg.App.Name == "" {
        errs = append(errs, "app.name wajib diisi")
    }
    if cfg.HTTP.Port <= 0 || cfg.HTTP.Port > 65535 {
        errs = append(errs, fmt.Sprintf("http.port %d tidak valid", cfg.HTTP.Port))
    }
    if cfg.Database.Host == "" {
        errs = append(errs, "database.host wajib diisi")
    }
    validLevels := map[string]bool{"debug": true, "info": true, "warn": true, "error": true}
    if !validLevels[cfg.Log.Level] {
        errs = append(errs, fmt.Sprintf("log.level %q tidak valid", cfg.Log.Level))
    }

    if len(errs) > 0 {
        return fmt.Errorf(strings.Join(errs, "; "))
    }
    return nil
}

// ── Main ──────────────────────────────────────────────────────

func main() {
    // Contoh config YAML
    exampleYAML := `
app:
  name: toko-online
  version: "1.2.0"
  env: production

http:
  host: "0.0.0.0"
  port: 443
  read_timeout: 5s
  write_timeout: 15s
  shutdown_timeout: 30s
  cors:
    enabled: true
    origins:
      - https://tokoonline.id
      - https://admin.tokoonline.id

database:
  host: db.internal
  port: 5432
  name: toko_production
  user: app_user
  password: rahasia123
  max_open_conns: 50
  max_idle_conns: 10
  conn_lifetime: 1h
  ssl_mode: require

redis:
  host: redis.internal
  port: 6379
  password: redis_secret
  db: 0
  timeout: 2s

log:
  level: info
  format: json
  output: stdout
`

    // Tulis ke file temp untuk demo
    tmpFile, _ := os.CreateTemp("", "config-*.yaml")
    tmpFile.WriteString(exampleYAML)
    tmpFile.Close()
    defer os.Remove(tmpFile.Name())

    cfg, err := LoadConfig(tmpFile.Name())
    if err != nil {
        log.Fatal("Gagal load config:", err)
    }

    fmt.Println("=== Konfigurasi Berhasil Dimuat ===")
    fmt.Printf("App         : %s v%s (%s)\n", cfg.App.Name, cfg.App.Version, cfg.App.Env)
    fmt.Printf("HTTP        : %s:%d\n", cfg.HTTP.Host, cfg.HTTP.Port)
    fmt.Printf("Read Timeout: %v\n", cfg.HTTP.ReadTimeout.Duration)
    fmt.Printf("CORS Origins: %v\n", cfg.HTTP.CORS.Origins)
    fmt.Printf("Database DSN: %s\n", cfg.Database.DSN())
    fmt.Printf("Redis Addr  : %s\n", cfg.Redis.Addr())
    fmt.Printf("Log Level   : %s (%s)\n", cfg.Log.Level, cfg.Log.Format)

    // Marshal kembali ke YAML (untuk verifikasi atau export)
    fmt.Println("\n=== Marshal Kembali ke YAML ===")
    output, _ := yaml.Marshal(cfg)
    fmt.Println(string(output))
}

Ringkasan #

  • gopkg.in/yaml.v3 adalah library YAML standar de-facto di Go — go get gopkg.in/yaml.v3.
  • Struct tags yaml:"name" untuk rename, omitempty untuk skip zero value, inline untuk flatten struct, flow untuk output satu baris.
  • yaml.Marshal untuk konversi Go ke YAML; yaml.Unmarshal untuk YAML ke Go — keduanya bekerja secara reflektif.
  • yaml.NewDecoder untuk multi-document YAML (dipisahkan ---) dan streaming file besar.
  • Custom MarshalYAML/UnmarshalYAML untuk tipe khusus — perlu implementasikan MarshalYAML() (interface{}, error) dan UnmarshalYAML(v *yaml.Node) error.
  • Selalu mulai dari default config dan override dengan file lalu environment variable — ini pola yang robust untuk aplikasi production.
  • Validasi config setelah load — jangan biarkan konfigurasi tidak valid sampai ke runtime.
  • Environment variable untuk secret (password, API key) — jangan tulis secret di file YAML yang di-commit ke git.
  • YAML untuk konfigurasi, JSON untuk API — ini pembagian tugas yang paling umum dan masuk akal.
  • Anchor (&) dan alias (*) di YAML memungkinkan reuse nilai — berguna untuk multi-environment config tanpa duplikasi.

← Sebelumnya: JSON   Berikutnya: MySQL →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact