Episode ini membahas cara mengubah schema protobuf tanpa melanggar kontrak: teknik versioning message dan field numbering, migrasi service bertahap, serta integrasi gRPC dengan database atau stateful backend menggunakan pola repository.

Aplikasi selalu berevolusi: field baru ditambahkan, tipe berubah, bahkan makna sebuah entitas bergeser. Di dunia REST, perubahan skema sering berarti versi API baru. Di gRPC, protobuf dirancang agar skema bisa berevolusi tanpa memutuskan client lama — jika kalian mengikuti aturannya.
Episode 8 membahas seni schema evolution: mengubah .proto tanpa breaking changes, menjaga nomor field tetap stabil, memigrasi service secara bertahap, dan menghubungkan kontrak gRPC dengan database nyata. Ini keterampilan yang paling menentukan umur panjang sebuah API.
Aturan emas protobuf: boleh menambahkan field, jangan mengubah yang sudah ada. Ketika server baru mengirim field baru, client lama hanya mengabaikannya; ketika client lama mengirim field yang tidak dikenal server, nilai itu disimpan sebagai unknown field. Kedua arah tetap kompatibel.
message Product {
string id = 1;
string name = 2;
double price = 3;
// tambahan: tanpa mengubah nomor yang sudah ada
string currency = 8;
}Menambahkan currency = 8 tidak mengganggu client yang hanya mengenal nomor 1 sampai 3. Nomor 8 dipilih karena berada di blok 1-byte (1 sampai 15) sambil menghindari bentrok.
Mengubah tipe field — misalnya dari int32 ke string — akan memproduksi data sampah atau error saat decoding. Ini aturan yang tidak bisa ditawar. Jika tipe benar-benar harus berubah, gunakan field baru dengan nomor baru dan deprecate yang lama.
Protobuf menyediakan kata kunci reserved untuk mengunci nomor dan nama field yang sudah dipensiunkan, sehingga tidak dipakai ulang secara tidak sengaja:
message Product {
reserved 4, 5;
reserved "old_price", "legacy_flag";
string id = 1;
string name = 2;
double price = 3;
string currency = 8;
}Baris reserved 4, 5 memastikan compiler menolak siapa pun yang mencoba memakai ulang nomor itu. Menggunakan ulang nomor lama adalah kesalahan paling berbahaya karena data lama akan terbaca dengan makna baru.
Untuk perubahan besar, gRPC memakai package versioning alih-alih mengubah kontrak lama. Ubah package catalog.v1 menjadi catalog.v2 dan buat file baru:
package catalog.v2;
message Product {
string id = 1;
string name = 2;
Money price = 3;
}
message Money {
int64 amount = 1;
string currency = 2;
}Client baru memakai catalog.v2, client lama tetap memakai catalog.v1. Keduanya bisa berjalan bersamaan di server yang sama karena service name-nya berbeda. Migrasi dilakukan bertahap: dua versi hidup bersama sampai client lama pensiun.
Hindari potongan besar. Alihkan traffic sedikit demi sedikit: 1 persen client ke versi baru, pantau metrik, lalu naikkan porsinya. catalog.v1 dipertahankan sebagai compat layer selama masa transisi dan dihapus hanya ketika tidak ada pemanggil tersisa.
Server gRPC sebaiknya memisahkan transport dari data. Pisahkan akses database dalam repository, sementara handler gRPC hanya memetakan tipe protobuf ke tipe domain:
type ProductRepo struct {
db *sql.DB
}
func (r *ProductRepo) FindByID(id string) (*pb.Product, error) {
row := r.db.QueryRow(
`SELECT id, name, price, currency FROM products WHERE id = $1`, id,
)
var p pb.Product
err := row.Scan(&p.Id, &p.Name, &p.Price, &p.Currency)
if errors.Is(err, sql.ErrNoRows) {
return nil, status.Error(codes.NotFound, "produk tidak ditemukan")
}
return &p, nil
}Perhatikan pemetaan: sql.ErrNoRows diterjemahkan menjadi codes.NotFound — error internal tidak bocor ke client. Query SQL di atas memakai placeholder $1 sehingga aman dari SQL injection.
Schema database berevolusi lewat migration yang berversi, misalnya dengan tool seperti golang-migrate:
migrate -path db/migrations -database "$DB_URL" upPerintah migrate ... up menerapkan semua migration yang belum dijalankan. Migration bersifat idempotent dan tercatat di tabel riwayat, sehingga state database selalu bisa direproduksi dari nol.
Inti yang harus dibawa pulang:
reserved.catalog.v1, catalog.v2) memungkinkan dua versi hidup bersama.Di episode 9 selanjutnya kita membahas health checking, reflection, dan service discovery — menerapkan standar grpc.health.v1.Health, mengaktifkan gRPC server reflection untuk debugging dengan grpcurl, dan memahami service discovery modern lewat DNS, Consul, Kubernetes, hingga xDS. Kontrak kalian yang sudah stabil kini siap dipantau dan ditemukan oleh sistem lain.