Implementation
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.
Transport Independence
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:
- Focus on implementing the core business logic without worrying about transport details
- Support multiple transports (gRPC, HTTP, etc.) with the same service implementation
- Test your business logic in isolation from transport concerns
The transport-specific code (gRPC in this case) is generated separately and adapts your service implementation to the specific protocol requirements.
Code Generation Overview
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-Specific Features
Streaming
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.
Best Practices
Error Handling
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
}
Context Usage
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
}
}
Resource Management
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.