Skip to main content

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:

service.proto
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:

  1. 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.
  2. GreeterServer, an interface for server code to implement. An implementor can be converted into a service definition with NewGreeterServer.
  3. UnimplementedGreeterServer, an empty struct that implements GreeterServer 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.
Want to use different serialization?

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: v2
managed:
enabled: true
plugins:
- remote: buf.build/protocolbuffers/go:v1.34.2
out: .
opt: paths=source_relative
- local: protoc-gen-go-restate
out: .
opt: paths=source_relative
inputs:
- directory: .

And to allow the import of dev/restate/sdk/go.proto, you can specify the dependency in your buf.yaml:

version: v2
lint:
use:
- DEFAULT
breaking:
use:
- FILE
deps:
- buf.build/restatedev/sdk-go

Then you can run buf generate to generate the code.