Immutable Object Pattern #
Dalam dunia pemrograman concurrent dan parallel, salah satu sumber bug paling berbahaya adalah shared mutable state. Ketika banyak goroutine (atau thread) mengakses dan memodifikasi data yang sama secara bersamaan, risiko terjadinya race condition, data corruption, dan bug yang sulit direproduksi meningkat drastis.
Salah satu solusi konseptual paling elegan untuk masalah ini adalah Immutable Object Pattern. Alih-alih mengunci data dengan mutex atau mekanisme sinkronisasi lain, pattern ini memilih pendekatan yang lebih fundamental: objek tidak pernah berubah setelah dibuat. Dengan begitu, objek tersebut aman untuk dibaca secara bersamaan oleh banyak goroutine tanpa koordinasi apa pun.
Artikel ini akan membahas Immutable Object Pattern dalam konteks concurrency, mulai dari definisi, tujuan, kapan sebaiknya digunakan, contoh implementasi di Golang, hingga best practice yang perlu diperhatikan.
Apa itu Immutable Object Pattern? #
Immutable Object Pattern adalah pola desain di mana sebuah objek:
- Seluruh state-nya ditentukan saat pembuatan
- Tidak menyediakan mekanisme untuk mengubah state setelah objek dibuat
- Jika dibutuhkan perubahan, maka dibuat objek baru, bukan memodifikasi objek lama
Dalam konteks concurrency, immutability memberikan jaminan bahwa:
- Tidak ada race condition pada objek tersebut
- Tidak diperlukan lock, mutex, atau atomic operation untuk membacanya
Contoh sederhana:
- ❌ Mutable object: struct dengan field yang bisa diubah kapan saja
- ✅ Immutable object: struct dengan field private, hanya bisa di-set lewat constructor
Tujuan Pattern #
Immutable Object Pattern memiliki beberapa tujuan utama:
Thread-Safety Secara Alami Objek immutable secara inheren aman digunakan di lingkungan concurrent.
Menghilangkan Race Condition Karena tidak ada operasi write, race condition menjadi mustahil.
Menyederhanakan Reasoning Kode Developer tidak perlu memikirkan urutan eksekusi goroutine atau siapa yang memodifikasi data.
Mengurangi Kompleksitas Sinkronisasi Mengurangi atau bahkan menghilangkan kebutuhan mutex dan lock.
Mendukung Functional Programming Style Cocok untuk pendekatan yang lebih deklaratif dan berbasis data flow.
Kapan Cocok Digunakan? #
Immutable Object Pattern sangat cocok digunakan pada kondisi berikut:
Data Konfigurasi Misalnya: config aplikasi, environment setting, feature flag snapshot.
Value Object Seperti Money, Coordinate, Token, RequestContext.
Message Passing Antar Goroutine Data yang dikirim lewat channel sebaiknya immutable.
Cache Read-Heavy Data jarang berubah, tapi sering dibaca secara concurrent.
Snapshot State Menyimpan state pada waktu tertentu (audit log, event sourcing, dsb).
Kurang Cocok Jika: #
- Objek sangat besar dan sering berubah
- Perubahan kecil tapi sangat sering (overhead alokasi objek baru terlalu mahal)
Contoh Implementasi (Golang) #
Contoh 1: Immutable Config Object #
package config
// Immutable struct
type AppConfig struct {
host string
port int
}
// Constructor
func NewAppConfig(host string, port int) *AppConfig {
return &AppConfig{
host: host,
port: port,
}
}
// Getter methods
func (c *AppConfig) Host() string {
return c.host
}
func (c *AppConfig) Port() int {
return c.port
}
Penggunaan secara concurrent:
func worker(cfg *config.AppConfig) {
fmt.Println(cfg.Host(), cfg.Port())
}
func main() {
cfg := config.NewAppConfig("localhost", 8080)
for i := 0; i < 10; i++ {
go worker(cfg)
}
time.Sleep(time.Second)
}
✅ Aman tanpa mutex karena AppConfig tidak bisa dimodifikasi.
Contoh 2: “Modifikasi” dengan Membuat Objek Baru #
type User struct {
id int
name string
}
func NewUser(id int, name string) *User {
return &User{id: id, name: name}
}
func (u *User) WithName(name string) *User {
return &User{
id: u.id,
name: name,
}
}
Penggunaan:
user1 := NewUser(1, "Alice")
user2 := user1.WithName("Bob")
// user1 tetap tidak berubah
Informasi Tambahan yang Relevan #
- Immutable Object sering dianggap sebagai zero-cost concurrency safety.
- Di Go, immutability adalah convention-based, bukan enforced oleh compiler.
- Banyak library concurrent modern memilih immutable data untuk performa dan keamanan.
Best Practice #
Gunakan Field Private Jangan ekspor field struct agar tidak bisa dimodifikasi dari luar.
Sediakan Constructor yang Jelas Pastikan semua state diinisialisasi saat pembuatan objek.
Hindari Setter Jangan menyediakan method
SetX().Hati-hati dengan Reference Type Slice, map, dan pointer bisa merusak immutability jika tidak di-copy.
❌ Buruk:
type Data struct { values []int }✅ Lebih aman:
type Data struct { values []int } func NewData(v []int) *Data { copied := append([]int{}, v...) return &Data{values: copied} }Dokumentasikan sebagai Immutable Jelaskan di komentar bahwa struct bersifat immutable.
Kombinasikan dengan Pattern Lain Cocok dipadukan dengan:
- Actor Model
- Message Passing
- Copy-on-Write
Penutup #
Immutable Object Pattern adalah salah satu pola paling kuat dan sederhana dalam concurrency. Dengan menghilangkan mutability, kita menghilangkan seluruh kelas bug yang berkaitan dengan race condition dan sinkronisasi.
Di Golang, meskipun tidak ada keyword khusus untuk immutable object, pola ini tetap bisa diterapkan secara efektif melalui desain struct yang disiplin. Jika Anda sering berurusan dengan data yang dibaca oleh banyak goroutine, Immutable Object Pattern layak menjadi default choice sebelum mempertimbangkan mutex atau lock.
Jika data tidak bisa berubah, maka data tersebut tidak bisa rusak oleh concurrency.