This guide explains how to implement gRPC services using the code generated by Goa. For a step-by-step walkthrough of creating a complete gRPC service, see the gRPC Service Tutorial.
A key feature of Goa is that your business logic implementation remains
independent of the transport protocol. The service interfaces generated in
gen/service
are protocol-agnostic, allowing you to:
The transport-specific code (gRPC in this case) is generated separately and adapts your service implementation to the specific protocol requirements.
When you run goa gen
, Goa generates several components:
gen/
├── grpc/
│ ├── pb/ # Protocol Buffer generated code
│ │ └── service.pb.go
│ ├── client/ # gRPC client code
│ │ └── client.go
│ └── server/ # gRPC server code
│ └── server.go
└── service/ # Service interfaces and types
└── service.go
For detailed instructions on generating and understanding these components, refer to the Implementing the Service tutorial section.
gRPC supports server-side, client-side, and bidirectional streaming. For detailed information about implementing streaming in Goa, including examples and best practices, see the dedicated Streaming guide.
Use gRPC-specific error codes and include meaningful metadata:
import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func (s *calculator) Divide(ctx context.Context, p *calc.DividePayload) (*calc.DivideResult, error) {
if p.Divisor == 0 {
return nil, status.Error(codes.InvalidArgument, "division by zero")
}
quotient := float64(p.Dividend) / float64(p.Divisor)
return &calc.DivideResult{Quotient: quotient}, nil
}
Handle gRPC context cancellation and timeouts:
func (s *calculator) LongOperation(ctx context.Context, p *calc.LongOperationPayload) (*calc.LongOperationResult, error) {
select {
case <-ctx.Done():
return nil, status.Error(codes.Canceled, "operation canceled")
case result := <-s.processAsync(p):
return result, nil
}
}
Properly manage gRPC connections and resources:
type grpcServer struct {
server *grpc.Server
lis net.Listener
}
func NewGRPCServer(svc calc.Service) (*grpcServer, error) {
srv := grpc.NewServer()
calcsvr.Register(srv, svc)
lis, err := net.Listen("tcp", ":8080")
if err != nil {
return nil, err
}
return &grpcServer{
server: srv,
lis: lis,
}, nil
}
func (s *grpcServer) Start() error {
return s.server.Serve(s.lis)
}
func (s *grpcServer) Stop() {
s.server.GracefulStop()
s.lis.Close()
}
For a complete example of setting up a gRPC server with proper error handling, logging, and graceful shutdown, see the Server Setup section in the tutorial.