# gRPC Guide

Complete guide to gRPC transport in Goa - service design, streaming patterns, error handling, and Protocol Buffer integration.

Source: https://goa.design/docs/1-goa/grpc-guide/

Relative links resolve against the source URL above.


Goa provides comprehensive support for building gRPC services through its DSL and code generation. This guide covers service design, streaming patterns, error handling, and implementation.

## Overview

Goa's gRPC support includes:

- **Automatic Protocol Buffer Generation**: `.proto` files generated from your design
- **Type Safety**: End-to-end type safety from definition to implementation
- **Code Generation**: Server and client code generated automatically
- **Built-in Validation**: Request validation based on your design
- **Streaming Support**: All gRPC streaming patterns supported
- **Error Handling**: Comprehensive error handling with status code mapping

### Type Mapping

| Goa Type  | Protocol Buffer Type |
|-----------|---------------------|
| Int       | int32              |
| Int32     | int32              |
| Int64     | int64              |
| UInt      | uint32             |
| UInt32    | uint32             |
| UInt64    | uint64             |
| Float32   | float              |
| Float64   | double             |
| String    | string             |
| Boolean   | bool               |
| Bytes     | bytes              |
| ArrayOf   | repeated           |
| MapOf     | map                |

---

## Service Design

### Basic Service Structure

```go
var _ = Service("calculator", func() {
    Description("The Calculator service performs arithmetic operations")

    GRPC(func() {
        Meta("package", "calculator.v1")
        Meta("go.package", "calculatorpb")
    })

    Method("add", func() {
        Description("Add two numbers")
        
        Payload(func() {
            Field(1, "a", Int, "First operand")
            Field(2, "b", Int, "Second operand")
            Required("a", "b")
        })
        
        Result(func() {
            Field(1, "sum", Int, "Result of addition")
            Required("sum")
        })
    })
})
```

### Method Definition

Methods define operations with gRPC-specific settings:

```go
Method("add", func() {
    Description("Add two numbers")

    Payload(func() {
        Field(1, "a", Int, "First operand")
        Field(2, "b", Int, "Second operand")
        Required("a", "b")
    })

    Result(func() {
        Field(1, "sum", Int, "Result of addition")
        Required("sum")
    })

    Error("overflow", ErrorResult, "Integer overflow")

    GRPC(func() {
        Response(CodeOK)
        Response("overflow", CodeOutOfRange)
    })
})
```

### Message Types

#### Field Numbering

Use Protocol Buffer best practices:
- Numbers 1-15: Frequently occurring fields (1-byte encoding)
- Numbers 16-2047: Less frequent fields (2-byte encoding)

```go
Method("createUser", func() {
    Payload(func() {
        // Frequently used fields (1-byte encoding)
        Field(1, "id", String)
        Field(2, "name", String)
        Field(3, "email", String)

        // Less frequently used fields (2-byte encoding)
        Field(16, "preferences", func() {
            Field(1, "theme", String)
            Field(2, "language", String)
        })
    })
})
```

#### Metadata Handling

Send fields as gRPC metadata instead of message body:

```go
var CreatePayload = Type("CreatePayload", func() {
    Field(1, "name", String, "Name of account")
    TokenField(2, "token", String, "JWT token")
    Field(3, "metadata", String, "Additional info")
})

Method("create", func() {
    Payload(CreatePayload)
    
    GRPC(func() {
        // Send token in metadata
        Metadata(func() {
            Attribute("token")
        })
        // Only include specific fields in message
        Message(func() {
            Attribute("name")
            Attribute("metadata")
        })
        Response(CodeOK)
    })
})
```

#### Response Headers and Trailers

```go
Method("create", func() {
    Result(CreateResult)
    
    GRPC(func() {
        Response(func() {
            Code(CodeOK)
            Headers(func() {
                Attribute("id")
            })
            Trailers(func() {
                Attribute("status")
            })
        })
    })
})
```

---

## Streaming Patterns

> **Design Recap**: Streaming is defined at the design level using `StreamingPayload` and `StreamingResult`. The DSL is transport-agnostic — the same design works for both HTTP and gRPC. See [DSL Reference: Streaming](dsl-reference/#streaming) for design patterns. This section covers gRPC-specific streaming implementation.

gRPC supports three streaming patterns.

### Server-Side Streaming

Server sends multiple responses to a single client request:

```go
var _ = Service("monitor", func() {
    Method("watch", func() {
        Description("Stream system metrics")
        
        Payload(func() {
            Field(1, "interval", Int, "Sampling interval in seconds")
            Required("interval")
        })
        
        StreamingResult(func() {
            Field(1, "cpu", Float32, "CPU usage percentage")
            Field(2, "memory", Float32, "Memory usage percentage")
            Required("cpu", "memory")
        })
        
        GRPC(func() {
            Response(CodeOK)
        })
    })
})
```

Server implementation:

```go
func (s *monitorService) Watch(ctx context.Context, p *monitor.WatchPayload, stream monitor.WatchServerStream) error {
    ticker := time.NewTicker(time.Duration(p.Interval) * time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ctx.Done():
            return ctx.Err()
        case <-ticker.C:
            metrics := getSystemMetrics()
            if err := stream.Send(&monitor.WatchResult{
                CPU:    metrics.CPU,
                Memory: metrics.Memory,
            }); err != nil {
                return err
            }
        }
    }
}
```

### Client-Side Streaming

Client sends multiple requests, server sends single response:

```go
var _ = Service("analytics", func() {
    Method("process", func() {
        Description("Process stream of analytics events")
        
        StreamingPayload(func() {
            Field(1, "event_type", String, "Type of event")
            Field(2, "timestamp", String, "Event timestamp")
            Field(3, "data", Bytes, "Event data")
            Required("event_type", "timestamp", "data")
        })
        
        Result(func() {
            Field(1, "processed_count", Int64, "Number of events processed")
            Required("processed_count")
        })
        
        GRPC(func() {
            Response(CodeOK)
        })
    })
})
```

Server implementation:

```go
func (s *analyticsService) Process(ctx context.Context, stream analytics.ProcessServerStream) error {
    var count int64
    
    for {
        event, err := stream.Recv()
        if err == io.EOF {
            return stream.SendAndClose(&analytics.ProcessResult{
                ProcessedCount: count,
            })
        }
        if err != nil {
            return err
        }
        
        if err := processEvent(event); err != nil {
            return err
        }
        count++
    }
}
```

### Bidirectional Streaming

Both client and server send streams simultaneously:

```go
var _ = Service("chat", func() {
    Method("connect", func() {
        Description("Establish bidirectional chat connection")
        
        StreamingPayload(func() {
            Field(1, "message", String, "Chat message")
            Field(2, "user_id", String, "User identifier")
            Required("message", "user_id")
        })
        
        StreamingResult(func() {
            Field(1, "message", String, "Chat message")
            Field(2, "user_id", String, "User identifier")
            Field(3, "timestamp", String, "Message timestamp")
            Required("message", "user_id", "timestamp")
        })
        
        GRPC(func() {
            Response(CodeOK)
        })
    })
})
```

Server implementation:

```go
func (s *chatService) Connect(ctx context.Context, stream chat.ConnectServerStream) error {
    for {
        msg, err := stream.Recv()
        if err == io.EOF {
            return nil
        }
        if err != nil {
            return err
        }

        response := &chat.ConnectResult{
            Message:   msg.Message,
            UserID:    msg.UserID,
            Timestamp: time.Now().Format(time.RFC3339),
        }
        
        if err := stream.Send(response); err != nil {
            return err
        }
    }
}
```

### Upgrading Earlier Streaming Clients

Current Goa-generated streaming clients identify the stream protocol in request
metadata and send a typed envelope when a method has both `Payload` and
`StreamingPayload`. A server can temporarily accept clients generated by the
earlier protocol by adding this metadata at the API, service, or method level:

```go
Meta("grpc:stream:compat", "v1")
```

Upgrade servers first, then clients, and remove the metadata after every client
uses the current protocol. This compatibility mode only supports earlier
clients whose one-time method payload contains primitive values or arrays of
primitive values; use the current protocol for object, map, or union payloads.

---

## Error Handling

> **Design Recap**: Errors are defined at the design level using the `Error` DSL at API, service, or method scope. See [DSL Reference: Error Handling](dsl-reference/#error-handling-design-level) for design patterns. This section covers gRPC-specific status code mapping.

### Status Codes

Map errors to gRPC status codes:

```go
Method("divide", func() {
    Error("division_by_zero")
    Error("invalid_input")

    GRPC(func() {
        Response(CodeOK)
        Response("division_by_zero", CodeInvalidArgument)
        Response("invalid_input", CodeInvalidArgument)
    })
})
```

Common status code mappings:

| Goa Error | gRPC Status Code | Use Case |
|-----------|-----------------|-----------|
| `not_found` | `CodeNotFound` | Resource doesn't exist |
| `invalid_argument` | `CodeInvalidArgument` | Invalid input |
| `internal_error` | `CodeInternal` | Server error |
| `unauthenticated` | `CodeUnauthenticated` | Missing/invalid credentials |
| `permission_denied` | `CodePermissionDenied` | Insufficient permissions |

### Error Definitions

Define errors at service or method level:

```go
var _ = Service("users", func() {
    // Service-level errors
    Error("not_found", func() {
        Description("User not found")
    })
    Error("invalid_input")

    Method("getUser", func() {
        // Method-specific error
        Error("profile_incomplete")

        GRPC(func() {
            Response(CodeOK)
            Response("not_found", CodeNotFound)
            Response("invalid_input", CodeInvalidArgument)
            Response("profile_incomplete", CodeFailedPrecondition)
        })
    })
})
```

### Returning Errors

Use generated error constructors:

```go
func (s *users) CreateUser(ctx context.Context, p *users.CreateUserPayload) (*users.User, error) {
    exists, err := s.db.EmailExists(ctx, p.Email)
    if err != nil {
        return nil, users.MakeDatabaseError(fmt.Errorf("failed to check email: %w", err))
    }
    if exists {
        return nil, users.MakeDuplicateEmail(fmt.Sprintf("email %s is already registered", p.Email))
    }
    
    user, err := s.db.CreateUser(ctx, p)
    if err != nil {
        return nil, users.MakeDatabaseError(fmt.Errorf("failed to create user: %w", err))
    }
    
    return user, nil
}
```

---

## Implementation

### Server Implementation

```go
package main

import (
    "log"
    "net"

    "google.golang.org/grpc"

    "github.com/yourusername/calc"
    gencalc "github.com/yourusername/calc/gen/calc"
    genpb "github.com/yourusername/calc/gen/grpc/calc/pb"
    gengrpc "github.com/yourusername/calc/gen/grpc/calc/server"
)

func main() {
    svc := calc.New()
    endpoints := gencalc.NewEndpoints(svc)
    svr := grpc.NewServer()
    gensvr := gengrpc.New(endpoints, nil)
    genpb.RegisterCalcServer(svr, gensvr)
    
    lis, err := net.Listen("tcp", ":8080")
    if err != nil {
        log.Fatal(err)
    }
    
    log.Println("gRPC server listening on :8080")
    svr.Serve(lis)
}
```

### Client Implementation

```go
package main

import (
    "context"
    "log"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    
    gencalc "github.com/yourusername/calc/gen/calc"
    genclient "github.com/yourusername/calc/gen/grpc/calc/client"
)

func main() {
    conn, err := grpc.Dial("localhost:8080",
        grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    grpcClient := genclient.NewClient(conn)
    client := gencalc.NewClient(
        grpcClient.Add(),
        grpcClient.Multiply(),
    )

    result, err := client.Add(context.Background(), &gencalc.AddPayload{A: 1, B: 2})
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("1 + 2 = %d", result)
}
```

---

## Protocol Buffer Integration

### Automatic Generation

Goa automatically generates `.proto` files from your design:

```protobuf
syntax = "proto3";
package calc;

service Calc {
    rpc Add (AddRequest) returns (AddResponse);
    rpc Multiply (MultiplyRequest) returns (MultiplyResponse);
}

message AddRequest {
    int64 a = 1;
    int64 b = 2;
}

message AddResponse {
    int64 result = 1;
}
```

### Protoc Configuration

```go
var _ = Service("calculator", func() {
    GRPC(func() {
        Meta("protoc:path", "protoc")
        Meta("protoc:version", "v3")
        Meta("protoc:plugin", "grpc-gateway")
    })
})
```

---

---

## See Also

- [DSL Reference: Streaming](dsl-reference/#streaming) — Design-level streaming patterns
- [DSL Reference: Error Handling](dsl-reference/#error-handling-design-level) — Design-level error definitions
- [HTTP Guide](http-guide/) — HTTP transport features
- [Error Handling Guide](error-handling/) — Complete error handling patterns
- [Clue Documentation](../3-ecosystem/clue/) — gRPC interceptors for observability

---

## Best Practices

### Error Handling
- Use appropriate gRPC status codes
- Include meaningful error messages
- Handle context cancellation and timeouts

### Streaming
- Keep message sizes reasonable
- Implement proper flow control
- Set appropriate timeouts
- Handle EOF and errors gracefully

### Performance
- Use appropriate field types
- Consider message size in design
- Use streaming for large datasets

### Versioning
- Plan for backward compatibility
- Use field numbers strategically
- Consider package versioning

### Resource Management
- Properly manage gRPC connections
- Implement graceful shutdown
- Clean up resources on context cancellation

