โ† Semua pembelajaran / Go Nol โ†’ Enterprise
Fase 11 ยท Enterprise & Capstone

gRPC untuk komunikasi antar layanan

Kalau kamu memang memisahkan layanan, gRPC memberi kontrak yang dijaga compiler di kedua sisi. Untuk API yang dipanggil browser, REST atau GraphQL hampir selalu pilihan yang lebih tepat.

Sumber asli grpc.io Resmi Rangkuman ~6 menit baca

Intisari

  • Kontrak ditulis di .proto, kode klien dan server digenerate โ€” kesalahan kontrak jadi error kompilasi.
  • Protobuf biner di atas HTTP/2: lebih kecil dan lebih cepat daripada JSON.
  • Browser tidak bisa memanggil gRPC langsung โ€” butuh gRPC-Web atau gateway.
  • Streaming dua arah bawaan; berguna untuk umpan dan pemrosesan besar.
  • Untuk layanan internal antar tim, kontrak bertipe menghapus seluruh kategori kesalahpahaman.

Kontraknya

syntax = "proto3";

package katalog.v1;
option go_package = "contoh.com/toko/gen/katalog/v1;katalogv1";

service Katalog {
  rpc AmbilProduk(AmbilProdukRequest) returns (Produk);
  rpc DaftarProduk(DaftarProdukRequest) returns (DaftarProdukResponse);
  rpc PantauStok(PantauStokRequest) returns (stream PerubahanStok);
}

message Produk {
  int64  id    = 1;
  string nama  = 2;
  int64  harga = 3;
  // Nomor field adalah kontrak sesungguhnya โ€” JANGAN pernah diubah
  // atau dipakai ulang. Nama boleh berubah; nomor tidak.
}

message AmbilProdukRequest {
  int64 id = 1;
}
go get -tool google.golang.org/protobuf/cmd/protoc-gen-go
go get -tool google.golang.org/grpc/cmd/protoc-gen-go-grpc

buf generate      # atau protoc dengan daftar flag yang panjang

Server

type katalogServer struct {
	katalogv1.UnimplementedKatalogServer   // WAJIB di-embed: menjaga
	svc *katalog.Layanan                    // kompatibilitas saat proto bertambah
}

func (s *katalogServer) AmbilProduk(ctx context.Context,
	req *katalogv1.AmbilProdukRequest) (*katalogv1.Produk, error) {

	p, err := s.svc.Ambil(ctx, req.GetId())
	switch {
	case errors.Is(err, katalog.ErrTidakDitemukan):
		return nil, status.Errorf(codes.NotFound, "produk %d tidak ada", req.GetId())
	case err != nil:
		// Jangan bocorkan detail internal ke pemanggil (Fase 1).
		s.log.ErrorContext(ctx, "ambil produk gagal", "err", err)
		return nil, status.Error(codes.Internal, "terjadi kesalahan")
	}

	return &katalogv1.Produk{
		Id: p.ID, Nama: p.Nama, Harga: int64(p.Harga),
	}, nil
}

func main() {
	srv := grpc.NewServer(
		grpc.ChainUnaryInterceptor(
			pulihkanInterceptor(log),      // pemulih panic (Fase 1)
			logInterceptor(log),
			authInterceptor(auth),
		),
		grpc.StatsHandler(otelgrpc.NewServerHandler()),   // trace (Fase 9)
	)
	katalogv1.RegisterKatalogServer(srv, &katalogServer{svc: svc})

	lis, _ := net.Listen("tcp", ":9090")
	srv.Serve(lis)
}

Interceptor adalah middleware versi gRPC, dan urutannya sama pentingnya dengan di HTTP (Fase 3): pemulih panic terluar, lalu log, lalu autentikasi. Ekosistemnya juga sudah menyediakan instrumentasi OpenTelemetry, sehingga trace tetap menyambung melintasi batas layanan.

Klien

conn, err := grpc.NewClient("katalog.internal:9090",
	grpc.WithTransportCredentials(insecure.NewCredentials()),   // di dalam VPC
	grpc.WithStatsHandler(otelgrpc.NewClientHandler()),
	grpc.WithDefaultServiceConfig(`{
		"methodConfig": [{
			"name": [{"service": "katalog.v1.Katalog"}],
			"retryPolicy": {
				"maxAttempts": 3,
				"initialBackoff": "0.1s",
				"maxBackoff": "1s",
				"backoffMultiplier": 2,
				"retryableStatusCodes": ["UNAVAILABLE", "DEADLINE_EXCEEDED"]
			}
		}]
	}`))
if err != nil {
	return err
}
defer conn.Close()

klien := katalogv1.NewKatalogClient(conn)

ctx, batal := context.WithTimeout(ctx, 2*time.Second)
defer batal()

p, err := klien.AmbilProduk(ctx, &katalogv1.AmbilProdukRequest{Id: 42})

Kebijakan retry ada di konfigurasi, bukan di kodemu โ€” termasuk backoff dan daftar kode status yang layak diulang. Ini salah satu keuntungan nyata gRPC dibanding klien HTTP buatan sendiri (Fase 3), di mana semua itu harus ditulis dan diuji sendiri.

gRPC versus REST

gRPCREST + JSON
KontrakDijaga compilerOpenAPI (Fase 7), atau tidak sama sekali
Ukuran payloadLebih kecil (biner)Lebih besar
Bisa dipanggil browserTidak langsungYa
Debug dengan curlButuh grpcurlMudah
Bisa di-cache CDNTidakYa (Fase 9)
StreamingBawaan, dua arahSSE / WebSocket
Melalui ALBYa (target group gRPC)Ya
Cocok untukAntar layanan internalAPI publik, browser

Untuk situs bertrafik baca tinggi โ€” sasaran roadmap ini โ€” poin "bisa di-cache CDN" biasanya menentukan. API publik berbasis HTTP dengan header Cache-Control yang benar bisa dilayani dari edge; gRPC tidak. Pakai gRPC di belakang, HTTP di depan.

Menjaga kompatibilitas proto

PerubahanAman?
Menambah field dengan nomor baruโœ…
Menambah method baruโœ…
Mengganti nama field (nomor tetap)โœ… di kabel; โŒ untuk kode yang memakainya
Mengubah nomor fieldโŒ merusak semuanya
Mengubah tipe fieldโŒ
Menghapus fieldโš ๏ธ tandai reserved supaya nomornya tidak dipakai ulang
Mengganti nama paket/serviceโŒ โ€” buat versi baru: katalog.v2

buf breaking memeriksa perubahan yang merusak kompatibilitas di CI, dengan membandingkan proto-mu terhadap cabang utama. Untuk kontrak yang dipakai tim lain, ini gerbang yang sama pentingnya dengan tes.

Latihan: definisikan satu service gRPC dengan dua method, generate kodenya, dan panggil dari klien Go. Lalu ubah nomor sebuah field di .proto, generate ulang hanya di sisi server, dan amati apa yang diterima klien lama โ€” itu alasan nomor field disebut kontrak sesungguhnya.

Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.