Implementation

Learn how to implement gRPC services using Goa’s generated code, including server and client implementations

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:

  1. Focus on implementing the core business logic without worrying about transport details
  2. Support multiple transports (gRPC, HTTP, etc.) with the same service implementation
  3. 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.