Memahami Optional Params di Golang
Daftar Isi
- Memahami Optional Params di Golang dengan Contoh Utility GeneratePassword
- Kenapa Perlu Optional Params?
- Beberapa Pendekatan (dan Kekurangannya)
- a. Versi “Kebelet” – Semua Jadi Param
- b. Pakai Struct Langsung
- c. Functional Options Pattern (Pendekatan yang Kita Pakai)
- Lalu, gimana implementasinya?
- Definisikan Config
- Definisikan Tipe Option
- Buat Helper WithXxx(...)
- 3.4. Implementasi GeneratePassword
- Sample Code
- Output
- Mini Arsitektur: Posisi GeneratePassword di Aplikasi
- Struktur Folder Sederhana
- Flow Arsitektur (High-Level)
- Kapan Sebaiknya Pakai Functional Options?
- 7. Penutup
Memahami Optional Params di Golang dengan Contoh Utility GeneratePassword
Halo BeeGangs, kali ini kita akan bahas salah satu pola yang sering banget dipakai di Golang untuk membuat fungsi lebih fleksibel tanpa bikin parameter-nya panjang dan berantakan: Optional Parameters.
Seperti yang kita tahu, Go tidak mendukung optional params seperti bahasa lain (misalnya Python atau TypeScript). Jadi, kalau mau bikin fungsi dengan banyak konfigurasi, kita punya beberapa pilihan:
- Pakai struct sebagai input
- Pakai variadic functions
- Pakai Functional Options Pattern (favorit banyak orang)
Di artikel ini, aku bakal pakai contoh sederhana: sebuah fungsi yang bisa dikonfigurasi memakai parameter opsional:
GeneratePassword() , algo, dan salt.secretKey
Kenapa Perlu Optional Params?
Gampangnya begini: ketika sebuah fungsi berkembang, sering ada kebutuhan untuk menambah konfigurasi baru. Kalau semua opsi dijadikan parameter, fungsi akan seperti ini:
1func GeneratePassword(password, algo, salt, secretKey string) string
2Di banyak bahasa lain, kamu bisa bikin parameter opsional atau default value, contoh (pseudo):
1function generatePassword(password: string, algo = "sha256", salt?: string, secretKey?: string) {}
2Di Go… nggak ada fitur ini. Go murni: kalau mau parameter, ya tulis semua:
1func GeneratePassword(password, algo, salt, secretKey string) string
2Problem:
- Semua param wajib diisi, padahal kadang cuma butuh
ajapassword - Urutan param gampang ketuker (apalagi kalau string semua)
- Kode pemanggilan jadi susah dibaca:
Padahal lebih enak kalau bisa nulis yang “bermakna”:
1GeneratePassword("bee123", WithSecretKey("supersecret"))
2Nah, di sinilah Functional Options Pattern masuk.
Beberapa Pendekatan (dan Kekurangannya)
a. Versi “Kebelet” – Semua Jadi Param
1func GeneratePassword(password, algo, salt, secretKey string) string
2- ✅ Simpel
- ❌ Wajib isi semua param
- ❌ Sulit dibaca dan rawan tertukar
- ❌ Kalau mau nambah param baru → function signature berubah → semua call site harus di-update
b. Pakai Struct Langsung
1type PasswordConfig struct {
2 Algo string
3 Salt string
4 SecretKey string
5}
6
7func GeneratePassword(password string, cfg PasswordConfig) string
8Pemanggilan:
1GeneratePassword("bee123", PasswordConfig{
2 Algo: "sha256",
3 Salt: "noobee",
4 SecretKey: "supersecret",
5})
6- ✅ Lebih jelas (pakai field name)
- ✅ Bisa punya default di dalam fungsi
- ❌ Masih agak verbose di pemanggilan
- ❌ Tiap kali panggil, harus bikin struct (walau bisa di-simplify)
Ini oke buat banyak kasus, tapi masih bisa kita buat lebih enak lagi.
c. Functional Options Pattern (Pendekatan yang Kita Pakai)
Idenya:
- Kita punya config struct (
)PasswordConfig - Kita punya tipe Option: function yang menerima pointer ke config dan memodifikasinya
terimaGeneratePasswordopts ...Option- Di dalam
, kita:GeneratePassword
1out2 := GeneratePassword(
2 "bee123",
3 WithSalt("noobee"),
4 WithSecretKey("supersecret"),
5)Keuntungan:
- Bisa punya default yang jelas
- Pemanggilan fleksibel: user bebas pilih opsi mana yang mau diisi
- Kita bisa nambah opsi baru tanpa mengubah function signature
Lalu, gimana implementasinya?
Definisikan Config
1type PasswordConfig struct {
2 Algo string
3 Salt string
4 SecretKey string
5}
6Ini “sumber kebenaran” konfigurasi untuk .GeneratePassword
Definisikan Tipe Option
1type Option func(*PasswordConfig)
2Artinya: adalah fungsi yang menerima pointer ke OptionPasswordConfig dan boleh mengubah isi config-nya.
Buat Helper WithXxx(...)
Di sinilah “optional param” muncul, tapi dalam bentuk fungsi.
1func WithAlgo(algo string) Option {
2 return func(c *PasswordConfig) {
3 c.Algo = algo
4 }
5}
6
7func WithSalt(salt string) Option {
8 return func(c *PasswordConfig) {
9 c.Salt = salt
10 }
11}
12
13func WithSecretKey(key string) Option {
14 return func(c *PasswordConfig) {
15 c.SecretKey = key
16 }
17}
18Cara bacanya:
WithSalt("noobee")mengembalikanOption- Option itu ketika dieksekusi akan mengubah
di dalamcfg.SaltGeneratePassword
3.4. Implementasi GeneratePassword
Di sini kita gabungkan semuanya.
1func GeneratePassword(password string, opts ...Option) string {
2 // 1. Default config
3 cfg := &PasswordConfig{
4 Algo: "sha256",
5 Salt: "",
6 SecretKey: "",
7 }
8
9 // 2. Apply semua option yang dikirim
10 for _, opt := range opts {
11 opt(cfg)
12 }
13
14 // 3. Bentuk input akhir sesuai config
15 finalInput := password + cfg.Salt + cfg.SecretKey
16
17 // 4. Pilih algoritma hash sesuai cfg.Algo
18 switch cfg.Algo {
19 case "sha256":
20 h := sha256.Sum256([]byte(finalInput))
21 return hex.EncodeToString(h[:])
22 default:
23 // fallback ke sha256 kalau belum di-support
24 h := sha256.Sum256([]byte(finalInput))
25 return hex.EncodeToString(h[:])
26 }
27}
28Breakdown:
cfg := &PasswordConfig{...}→ Kita set default value. Misalnya defaultalgo = sha256.for _, opt := range opts { opt(cfg) }→ Di sinilah option-option sepertiWithSalt(...),WithSecretKey(...)dijalankan dan mengubah config.finalInput := password + cfg.Salt + cfg.SecretKey→ Contoh sederhana: kita gabungkan password, salt, dan secretKey, lalu di-hash.switch cfg.Algo { ... }→ Di contoh ini, baru supportsha256. Di real life, kamu bisa tambahkansha512,argon2, dsb.
Sample Code
1package main
2
3import (
4 "crypto/sha256"
5 "encoding/hex"
6 "fmt"
7)
8
9// ====== CONFIG & OPTION ======
10
11type PasswordConfig struct {
12 Algo string
13 Salt string
14 SecretKey string
15}
16
17type Option func(*PasswordConfig)
18
19func WithAlgo(algo string) Option {
20 return func(c *PasswordConfig) {
21 c.Algo = algo
22 }
23}
24
25func WithSalt(salt string) Option {
26 return func(c *PasswordConfig) {
27 c.Salt = salt
28 }
29}
30
31func WithSecretKey(key string) Option {
32 return func(c *PasswordConfig) {
33 c.SecretKey = key
34 }
35}
36
37// ====== CORE FUNCTION ======
38
39func GeneratePassword(password string, opts ...Option) string {
40 // default config
41 cfg := &PasswordConfig{
42 Algo: "sha256",
43 Salt: "",
44 SecretKey: "",
45 }
46
47 // apply options
48 for _, opt := range opts {
49 opt(cfg)
50 }
51
52 finalInput := password + cfg.Salt + cfg.SecretKey
53
54 // simple: hanya support sha256
55 h := sha256.Sum256([]byte(finalInput))
56 return hex.EncodeToString(h[:])
57}
58
59// ====== DEMO ======
60
61func main() {
62 // 1. Tanpa optional params (pakai default)
63 fmt.Println("=== Default (tanpa salt & secretKey) ===")
64 out1 := GeneratePassword("bee123")
65 fmt.Println(out1)
66
67 // 2. Dengan salt dan secretKey
68 fmt.Println("\n=== Dengan Salt dan SecretKey ===")
69 out2 := GeneratePassword(
70 "bee123",
71 WithSalt("noobee"),
72 WithSecretKey("supersecret"),
73 )
74 fmt.Println(out2)
75
76 // 3. Hanya dengan secretKey
77 fmt.Println("\n=== Hanya dengan SecretKey ===")
78 out3 := GeneratePassword(
79 "bee123",
80 WithSecretKey("supersecret"),
81 )
82 fmt.Println(out3)
83
84 // 4. Hanya dengan salt
85 fmt.Println("\n=== Hanya dengan Salt ===")
86 out4 := GeneratePassword(
87 "bee123",
88 WithSalt("noobee"),
89 )
90 fmt.Println(out4)
91}
92Output
Kalau kamu run dengan input persis di atas, contoh outputnya bisa seperti ini:
1=== Default (tanpa salt & secretKey) ===
248d22b9f8d3c069a995dcc3fb2faddfe192aaf98a6959ce3353dc5e17fc67e35
3
4=== Dengan Salt dan SecretKey ===
5f5199c4c0bfe9032b78418fa54e703afb05db057844ad4ff322dd6db442f4d73
6
7=== Hanya dengan SecretKey ===
89ddda548f01b133cb7e0ce4fbcd1d316a6aa665196e87d4e8a63961393215ca8
9
10=== Hanya dengan Salt ===
119f5f3e6ae9ca0f6b5ddfd8f61c350cdbcee48269ffdb5307f8c961520c3fecd8
12Yang penting di sini: Input berbeda → kombinasi salt/secretKey berbeda → output hash berbeda, tapi cara memanggil fungsi jadi jelas dan fleksibel.
Mini Arsitektur: Posisi GeneratePassword di Aplikasi
Biar lebih kebayang, kita taruh utility ini di konteks aplikasi beneran. Misalnya sistem registrasi user.
Struktur Folder Sederhana
1.
2├── cmd
3│ └── api
4│ └── main.go # entrypoint HTTP server
5├── internal
6│ ├── handler
7│ │ └── user_handler.go # HTTP handler register/login
8│ ├── service
9│ │ └── user_service.go # business logic
10│ └── security
11│ └── password.go # GeneratePassword + options
12└── go.mod
13Di kamu punya kode:internal/security/password.go
1package security
2
3// PasswordConfig, Option, WithSalt, WithSecretKey, GeneratePassword
4// (isi sama seperti contoh di atas, hanya beda package)
5Flow Arsitektur (High-Level)
Flow registrasi user:
- HTTP Handler nerima request:
- Handler panggil
- userService
- DB simpan
Secara “diagram teks”:
1[HTTP Request]
2 |
3 v
4[User Handler] --> parse JSON body
5 |
6 v
7[User Service] --> validasi data, panggil GeneratePassword()
8 |
9 v
10[security.GeneratePassword] + optional params (salt, secretKey, algo)
11 |
12 v
13[Database] --> simpan hash
14Contoh implementasi:
1// internal/service/user_service.go
2package service
3
4import (
5 "context"
6
7 "myapp/internal/security"
8)
9
10type UserService struct {
11 // repo, logger, dll.
12}
13
14func (s *UserService) Register(ctx context.Context, email, plainPassword string) error {
15 // misalnya salt = email, secretKey = dari env
16 passwordHash := security.GeneratePassword(
17 plainPassword,
18 security.WithSalt(email),
19 security.WithSecretKey("SUPER-SECRET-FROM-ENV"),
20 )
21
22 // TODO: simpan ke database (pseudo)
23 // return s.repo.CreateUser(ctx, email, passwordHash)
24 return nil
25}
26Dari arsitektur ini, kelihatan:
- Layer lain cuma tahu: “kalau mau hash password, panggil
- Detail teknis (pakai sha256, apa itu salt, dsb.) dikapsulasi di
- Kalau suatu saat kamu mau ganti algo ke
Kapan Sebaiknya Pakai Functional Options?
Cocok dipakai ketika:
- Fungsi punya banyak konfigurasi, tapi:
- Kamu ingin:
- Contoh use case:
Kurang perlu kalau:
- Parameternya cuma 1–2 dan hampir selalu diisi semua
- Fungsi jarang berubah dan simple
- Kadang pakai struct biasa sudah cukup
7. Penutup
Jadi, BeeGangs, pattern Functional Options ini sebenarnya bukan fitur spesial bahasa, tapi “trik” desain API di Golang yang bikin:
- Fungsi lebih fleksibel
- Call site lebih mudah dibaca
- Arsitektur lebih rapi dan scalable