tutorial

  1. Studi Kasus: Generate Protobuf Secara Otomatis untuk Banyak Bahasa

108. Studi Kasus: Generate Protobuf Secara Otomatis untuk Banyak Bahasa

Selama satu dekade terakhir, microservices telah mendominasi arsitektur sistem backend. Salah satu kunci suksesnya adalah kemampuan berbagai layanan untuk saling berkomunikasi melalui protokol yang konsisten, cepat, namun mudah dikembangkan. Salah satu format yang populer untuk kebutuhan ini adalah Protocol Buffers (Protobuf) yang diperkenalkan oleh Google.

Dalam artikel ini, saya akan berbagi pengalaman nyata mengenai bagaimana tim kami mengotomasi proses generate Protobuf menjadi kode untuk berbagai bahasa secara konsisten dan efisien. Studi kasus ini memadukan best practice, penggunaan alat otomatis, CI/CD, dan bagaimana hasil akhirnya mampu mengurangi human error serta mempercepat proses pengembangan layanan.


1. Permasalahan Awal pada Tim Lintas Stack

Tim kami terdiri dari engineer Go, Node.js, dan Java. Setiap perubahan skema Protobuf, harus diikuti dengan regenerate kode hasil dari file .proto ke masing-masing language binding. Pertama-tama, proses ini dilakukan manual:

  • Developer submit perubahan pada file .proto
  • Tim backend lain menarik perubahan main branch
  • Membuka terminal, generate kode sesuai bahasa (protoc-gen-go, protoc-gen-grpc-java, dsb)
  • Commit kode hasil generate ke repository service masing-masing

Sayangnya, alur manual ini membawa berbagai masalah:

  1. Sering terjadi ketidaksesuaian versi: Satu tim lupa melakukan generate, tim lain mendapat kode yang sudah berbeda.
  2. Potensi merge conflict tinggi: File hasil generate sering berbenturan.
  3. Menyita waktu: Terutama untuk layanan yang memakai banyak bahasa.
  4. Susah mengontrol tools dan plugin: Versi generator dan plugin sering berbeda-beda di mesin developer.

Ilustrasi Alur Manual

flowchart LR
    A[Update .proto files] --> B{Manual generate}
    B -- "Go" --> C[protoc-gen-go]
    B -- "Java" --> D[protoc-gen-grpc-java]
    B -- "Node.js" --> E[protoc-gen-ts]
    C --> F[Push ke repo Go]
    D --> G[Push ke repo Java]
    E --> H[Push ke repo Node.js]

2. Solusi: Otomatisasi Generate Protobuf Multibahasa

Untuk menanggulangi masalah di atas, kami memutuskan mengotomasi seluruh proses code generation di sistem CI/CD (GitHub Actions), serta membuat sebuah repository shared proto. Alur barunya:

  • Semua file .proto disimpan di satu repo
  • Setiap commit atau PR, GitHub Actions otomatis meng-generate language bindings sembari mengecek compatibility
  • File hasil generate di-commit ke branch terpisah, atau di-upload sebagai artifacts
  • Layanan Go, Node.js, dan Java tinggal mengambil release terbaru atau package kode hasil generate

Skema High Level

flowchart TD
    RepoProto["Shared Proto Repo (.proto)"]
    CI["CI/CD Workflow (GitHub Actions)"]
    GoPkg["Go SDK Artifacts / Package"]
    JavaPkg["Java SDK Artifacts / Package"]
    NodePkg["Node.js SDK Artifacts / Package"]
    ServiceA["Go Service (import paket)"]
    ServiceB["Node.js Service (import paket)"]
    ServiceC["Java Service (import paket)"]

    RepoProto --> CI
    CI --> GoPkg
    CI --> JavaPkg
    CI --> NodePkg
    GoPkg --> ServiceA
    JavaPkg --> ServiceC
    NodePkg --> ServiceB

3. Step-by-Step: Membangun Pipeline Protobuf Otomatis

Mari kita bedah tiap tahapnya, lengkap dengan contoh konfigurasinya.

3.1 Struktur Folder Repository

proto-shared/
├── protos/
│   ├── example/
│   │   ├── hello.proto
├── build/
│   ├── go/
│   ├── java/
│   ├── node/
├── .github/
│   └── workflows/
│       └── generate.yml

3.2. Contoh File .proto

// protos/example/hello.proto
syntax = "proto3";

package example;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

3.3. Definisi Workflow CI/CD (GitHub Actions)

Di dalam .github/workflows/generate.yml:

name: Generate Protobuf for Multiple Languages

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - name: Setup Go
        uses: actions/setup-go@v4
        with:
          go-version: '1.21.0'
      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: '18.x'
      - name: Setup Java
        uses: actions/setup-java@v3
        with:
          distribution: 'temurin'
          java-version: '17'

      - name: Install Protobuf Compiler
        run: sudo apt-get install -y protobuf-compiler

      - name: Install protoc plugins
        run: |
          go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
          go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
          npm install -g protoc-gen-ts

      - name: Generate Go code
        run: |
          mkdir -p build/go
          protoc -I=protos --go_out=build/go --go-grpc_out=build/go protos/example/*.proto

      - name: Generate Node.js code
        run: |
          mkdir -p build/node
          protoc -I=protos --js_out=import_style=commonjs:build/node --grpc_out=build/node protos/example/*.proto

      - name: Generate Java code
        run: |
          mkdir -p build/java
          protoc -I=protos --java_out=build/java --grpc-java_out=build/java protos/example/*.proto

      - name: Upload Build Artifacts
        uses: actions/upload-artifact@v3
        with:
          name: generated-protobuf
          path: build/

Penjelasan Singkat:

  • Setup environment tiga bahasa dalam satu workflow
  • Install protokol compiler & plugin sesuai kebutuhan multi-bahasa
  • Generate source code untuk Go, Node.js, dan Java ke folder build terpisah
  • Upload artifact yang bisa diambil setiap tim/service

4. Konsumsi Hasil Generate: Simulasi Service Go

Untuk tim Go, mereka tinggal mendownload artifacts dari CI/CD atau fetching dari sebuah release/package. Simulasi cara pemakaiannya:

import (
    "context"
    "log"
    pb "github.com/org/proto-shared/build/go/example"
)

func main() {
    conn, err := grpc.Dial("greeter-service:50051", grpc.WithInsecure())
    if err != nil {
       log.Fatal(err)
    }
    defer conn.Close()
    client := pb.NewGreeterClient(conn)
    resp, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: "Budi"})
    if err != nil {
        log.Fatal(err)
    }
    log.Println("Received:", resp.Message)
}

5. Tabel Perbandingan: Sebelum vs Sesudah Otomasi

KriteriaSebelum OtomasiSesudah Otomasi
Konsistensi versiSering bermasalahSangat terjaga
Start serviceSering errorHampir tidak pernah error
Workflow developerManual, repetitifHanya perlu update .proto
Integrasi CI/CDTidak adaFull otomatis
Waktu sinkronisasiSering telatOtomatis (hitungan menit)
Ketergantungan lokalTinggiSangat minim

6. Lessons Learned & Saran Implementasi

Lessons Learned:

  • Repository terpusat untuk .proto sangat memudahkan versioning
  • CI/CD wajib terotomasi agar tidak ada langkah manual
  • Build artifact lebih baik daripada langsung commit hasil generate untuk menghindari merge conflict

Saran Implementasi:

  • Jika sudah punya monorepo, gunakan submodule untuk tim yang punya repo sendiri.
  • Gunakan semantic versioning pada release .proto
  • Build juga docker image yang berisi result, jika perlu dipakai di container lain.

7. Penutup

Otomatisasi generate Protobuf untuk banyak bahasa telah membawa game-changer bagi proses pengembangan di tim kami. Proses yang tadinya rawan kesalahan dan memakan waktu kini menjadi elegan, scalable, dan mudah diintegrasikan ke pipeline manapun.

Jika Anda bekerja di tim multi-stack, jangan ragu menginvestasikan waktu untuk membangun fondasi ini! Keuntungan jangka panjangnya nyata — lebih cepat merilis fitur, sedikit error, dan integrasi antar tim yang jauh lebih seamless.

Silakan diskusi lebih lanjut jika ingin tahu detail setup atau butuh script lebih advance!

comments powered by Disqus