example-buf-rpc-service

module
v0.0.0-...-c313ff3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 19, 2025 License: MIT

README

Go gRPC/ConnectRPC Service using Buf (v2)

Annotated example of gRPC/ConnectRPC service implementations in Go, using Buf (v2 config) for code generation and .proto dependency management

  • buf.yaml contains single-module Buf workspace configuration, it's also where you specify BSR module dependencies for your modules (Buf shares deps list for all modules in a single workspace)

  • buf.gen.yaml configures code generation. This example uses Buf Managed Mode feature. Managed Mode allows removal of language-specific annotations from .proto files (for example: go_package, java_package) and supply it through configuration instead

    • Initially, Managed Mode was a source of confusion for me when trying to use Buf for Go code generation. I hope I can provide more clarifications for anyone that might feel the same. Buf Managed Mode for Go
  • proto folder contains Protobuf sources, written .proto with Managed Mode configuration in mind, it lacks option go_package that would be expected to generate Protobuf source in Go

  • api folder contains generated Go code from Protobuf compilations

  • pkg ConnectRPC and gRPC implementations

Buf Managed Mode for Go (go_package_prefix)

I wish there is simpler and shorter way to describe this problem better, but I can only explain it thoroughly and using example

To compile .proto into Go code, ALL .proto file MUST provide Go package's import. Traditionally, you specify this by using option go_package annotation inside .proto file.

Here is an example taken from google/type/datetime.proto that is authored WITH language-specific annotations.

// from: google/type/datetime.proto

// ...

package google.type;
import "google/protobuf/duration.proto";

// ...
option go_package = "google.golang.org/genproto/googleapis/type/datetime;datetime";
option java_multiple_files = true;
option java_outer_classname = "DateTimeProto";
option java_package = "com.google.type";
option objc_class_prefix = "GTP";

message DateTime {
    //...
}

We can observe how this go_package annotation being used in event of DateTime reuse (as dependency)

// file: proto/task/v1/task.proto

import "google/type/datetime.proto";

message Task {
    string id = 1;
    string title = 2;
    bool completed = 3;
    google.type.DateTime created_at = 4;
    google.type.DateTime updated_at = 5;
    google.type.DateTime deleted_at = 6;
}

If we look at the generated Go code, it includes import directive of that go_package specified in google/protobuf/datetime.photo to use DateTime struct as dependency.

// file: api/task/v1/task.pb.go
import (
  // This import match `go_package`
	datetime "google.golang.org/genproto/googleapis/type/datetime"
  // the rest of import
)

This approach is relatively okay from external .proto consumer side. Dependency resolution for Go becomes simple because it isolates transitive dependencies by simply using that Go package. Once you generate task.pb.go you just need to call go mod tidy after to resolve such package.

However, if you're the public .proto author, you have to maintain a public Go module/package if you don't want to force your .proto consumers to complicate their dependency management.

Buf Managed Mode aims to promote "consumer/language-agnostic" .proto definitions by removing language-specific annotations such as go_package from Protobuf sources to make it more reusable any consumer (e.g. someone outside org) and let consumers set their own go_package value depending on their project.

The task.proto definition in this project is authored WITHOUT any go_package or other language-specific annotations. But remember, go_package value MUST be provided for all .proto files for compiler to generate Go code, so we turn to buf.gen.yaml to tell the compiler how to determine go_package value for all .proto files.

My confusion with Managed Mode initially came from the assumption that Buf would have set default behavior to generate Go code by simply turning Managed Mode on.

# Wrong assumption: this should work...
managed:
  enabled: true

For Go specifically, Buf doesn't have enough default behavior preconfigured that would allow us to start generating Go code. You MUST still provide additional configuration in buf.gen.yaml. Confusingly, this configuration must be provided via override option

managed:
  enabled: true
  override:
    - file_option: go_package_prefix
      value: github.com/nadhifikbarw/example-buf-rpc-service/api

When you set go_package_prefix "override". go_package value for all .proto files would follow the rules of:

<prefix>/<proto_package_parts>

For example, my prefix is github.com/nadhifikbarw/example-buf-rpc-service/api so task.proto with package value task.v1 would be equivalent to setting:

option go_package = "github.com/nadhifikbarw/example-buf-rpc-service/api/task/v1";

You can disable Managed Mode behavior for certain .proto files (check config reference for granularity). For some cases, you sometimes SHOULD disable.

For example, I want to use Google generated protobuf types (therefore respecting go_package annotation in google/type/datetime.proto). This way, my generated task.pb.go keeps google.golang.org/genproto/googleapis/type/datetime import.

managed:
  enabled: true
  override:
    - file_option: go_package_prefix
      value: github.com/nadhifikbarw/example-buf-rpc-service/api
    # Disable Managed Mode behavior for `.proto` sources from this BSR module.
    # This will make compiler respect explicit `go_package` annotation
  disable:
    - file_option: go_package_prefix
      module: buf.build/googleapis/googleapis

Picking ConnectRPC or gRPC

In case you only learned about ConnectRPC. ConnectRPC is an alternative RPC framework created by Buf. While it should be considered as seperate RPC protocol, it maintains gRPC-compatibility. ConnectRPC is now a Cloud Native Computing Foundation (CNCF) sandbox project.

In actual project you obviously don't need to implement both. I implemented both here for exploration purposes. ConnectRPC provides much more convenient mechanism to handle web-based RPC client. If your requirements involve allowing RPC call from browser consider Connect Protocol

Buf only supports pushed Buf module (BSR module) for remote dependencies

Dependencies are shared between all modules in the workspace. The value must be a valid path to a BSR module (Buf module that has been pushed to BSR). This means that if you have a module you want to use as a dependency, it must also be pushed to the BSR.

Service Implementations

The service implementations are not production-grade, it's trivial to showcase differences between gRPC/ConnectRPC implementations style.

ConnectRPC allows RPC via HTTP easily

curl --header "Content-Type: application/json" --data "{\"title\": \"Task Title\"}" http://localhost:8080/task.v1.TaskService/ListTasks

gRPC-server uses grpcurl (I enabled reflection)

grpcurl -plaintext localhost:3000 task.v1.TaskService/ListTasks

Directories

Path Synopsis
api
pkg
cmd/grpc-server command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL