Code generation
The Go SDK supports generating typed clients and server interfaces from Protocol Buffer service definitions. A full example can be found in the SDK repository.
Code generation relies on the tool protoc-gen-go-restate
which you can install using
go install github.com/restatedev/sdk-go/protoc-gen-go-restate@latest
, in addition to protoc
which you can install from the Protobuf compiler repo.
Defining a service
To get started, create a Protocol Buffer definition file defining a service:
package greeter;service Greeter {rpc SayHello (HelloRequest) returns (HelloResponse) {}}message HelloRequest {string name = 1;}message HelloResponse {string message = 1;}
You can compile this file into Go types and your Restate generated code with:
protoc --go_out=. --go_opt=paths=source_relative --go-restate_out=. --go-restate_opt=paths=source_relative proto/service.proto
This creates two files: service.pb.go
containing Go types, and service_restate.pb.go
which contains several useful objects:
NewGreeterClient
, which returns an interface for making type-safe calls to the Greeter service. The default serialization for request and response data is the canonical Protobuf JSON encoding, but this is configurable.GreeterServer
, an interface for server code to implement. An implementor can be converted into a service definition withNewGreeterServer
.UnimplementedGreeterServer
, an empty struct that implementsGreeterServer
by just returning 'not implemented' terminal errors for each method. You can choose to embed this struct into your own implementation struct, in which case new methods in the Protobuf definition will not cause compilation errors in your Go code, which can help with backwards compatibility.
Both NewGreeterClient
and NewGreeterServer
accept option parameters which can be used to set a different serialization codec.
For example, you can use Protobuf by providing restate.WithProto
to both functions.
Implementing the server
Next, you can implement the GreeterServer
interface and bind it to your Restate server.
type greeter struct {proto.UnimplementedGreeterServer}func (greeter) SayHello(ctx restate.Context, req *proto.HelloRequest) (*proto.HelloResponse, error) {return &proto.HelloResponse{Message: fmt.Sprintf("%s!", req.Name),}, nil}func main() {if err := server.NewRestate().Bind(proto.NewGreeterServer(greeter{})).Start(context.Background(), ":9080"); err != nil {log.Fatal(err)}}
Using the client
NewGreeterClient
will return a similar client to what you'd get from restate.Service
, with additional
type safety for method names and request/response types.
client := proto.NewGreeterClient(ctx)resp, err := client.SayHello().Request(&proto.HelloRequest{Name: "world"})if err != nil {return err}
Defining Virtual Objects
To define a Virtual Object, you will need to provide the option dev.restate.sdk.go.service_type
when defining the service.
This option is defined by the Go SDK's proto file
dev/restate/sdk/go.proto
which must be imported in your own file,
and a directory containing it must be provided to protoc eg with -I $GOPATH/src/github.com/restatedev/sdk-go/proto
You may additionally provide the option dev.restate.sdk.go.handler_type
to denote that a handler only needs access to the ObjectSharedContext
and can run
concurrently with other handlers.
import "dev/restate/sdk/go.proto";service Counter {option (dev.restate.sdk.go.service_type) = VIRTUAL_OBJECT;rpc Add (AddRequest) returns (AddResponse) {}rpc Get (GetRequest) returns (GetResponse) {option (dev.restate.sdk.go.handler_type) = SHARED;}}
Virtual Object clients are instantiated with a key:
client := proto.NewCounterClient(ctx, "key-1")resp, err := client.Add().Request(&proto.AddRequest{Delta: 1})if err != nil {return err}
Easier Protobuf with Buf
Instead of manually wrangling protoc
to handle generation and proto imports,
it can be a lot easier to use Buf.
Buf determines what code to generate based on a buf.gen.yaml
file:
version: v2managed:enabled: trueplugins:- remote: buf.build/protocolbuffers/go:v1.34.2out: .opt: paths=source_relative- local: protoc-gen-go-restateout: .opt: paths=source_relativeinputs:- directory: .
And to allow the import of dev/restate/sdk/go.proto
, you can specify the dependency in your buf.yaml
:
version: v2lint:use:- DEFAULTbreaking:use:- FILEdeps:- buf.build/restatedev/sdk-go
Then you can run buf generate
to generate the code.