# Guía gRPC

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

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

Relative links resolve against the source URL above.


Goa proporciona un soporte completo para la construcción de servicios gRPC a través de su DSL y la generación de código. Esta guía cubre el diseño de servicios, patrones de streaming, gestión de errores e implementación.

## Visión general

El soporte gRPC de Goa incluye:

- **Generación automática de búferes de protocolo**: archivos `.proto` generados a partir de su diseño
- **Seguridad de tipos**: Seguridad de tipos de extremo a extremo desde la definición hasta la implementación
- **Generación de código**: Código de servidor y cliente generado automáticamente
- **Validación incorporada**: Validación de solicitudes basada en su diseño
- **Soporte de streaming**: Soporte de todos los patrones gRPC
- **Gestión de errores**: Gestión integral de errores con asignación de códigos de estado

### Asignación de tipos

| Tipo de Goa | Tipo de Buffer de Protocolo |
|-----------|---------------------|
| 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 |

---

## Diseño del servicio

### Estructura básica del servicio

```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")
        })
    })
})
```

### Definición del método

Los métodos definen operaciones con ajustes específicos de gRPC:

```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)
    })
})
```

### Tipos de mensajes

#### Numeración de campos

Utilice las mejores prácticas de Protocol Buffer:
- Números 1-15: Campos frecuentes (codificación de 1 byte)
- Números 16-2047: Campos menos frecuentes (codificación de 2 bytes)

```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)
        })
    })
})
```

#### Gestión de metadatos

Enviar campos como metadatos gRPC en lugar del cuerpo del mensaje:

```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)
    })
})
```

#### Cabeceras de respuesta y trailers

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

---

## Patrones de streaming

> **Recapitulación del diseño**: El streaming se define a nivel de diseño usando `StreamingPayload` y `StreamingResult`. El DSL es agnóstico al transporte - el mismo diseño funciona tanto para HTTP como para gRPC. Ver [Referencia DSL: Streaming](dsl-reference/#streaming) para patrones de diseño. Esta sección cubre la implementación de streaming específica de gRPC.

gRPC soporta tres patrones de streaming.

### Streaming del lado del servidor

El servidor envía múltiples respuestas a una única petición del cliente:

```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)
        })
    })
})
```

Implementación del servidor:

```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
            }
        }
    }
}
```

### Streaming del lado del cliente

El cliente envía múltiples peticiones, el servidor envía una única respuesta:

```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)
        })
    })
})
```

Implementación del servidor:

```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++
    }
}
```

### Streaming bidireccional

Tanto el cliente como el servidor envían flujos simultáneamente:

```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)
        })
    })
})
```

Implementación del servidor:

```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
        }
    }
}
```

### Actualizar clientes de streaming anteriores

Los clientes de streaming generados por la versión actual de Goa identifican el protocolo en
los metadatos de la solicitud y envían una envoltura tipada cuando un método
tiene `Payload` y `StreamingPayload`. Un servidor puede aceptar temporalmente
clientes generados por el protocolo anterior añadiendo estos metadatos al nivel
de API, servicio o método:

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

Actualice primero los servidores, después los clientes y elimine los metadatos
cuando todos los clientes usen el protocolo actual. Este modo de compatibilidad
solo admite clientes anteriores cuyo payload único contiene valores primitivos
o arreglos de valores primitivos; use el protocolo actual para payloads de
objetos, mapas o uniones.

---

## Tratamiento de errores

**Recapitulación del diseño**: Los errores se definen a nivel de diseño utilizando el DSL `Error` en el ámbito de la API, servicio o método. Ver [Referencia DSL: Tratamiento de errores](dsl-reference/#tratamiento-de-errores-nivel-de-diseño) para patrones de diseño. Esta sección cubre el mapeo de códigos de estado específico de gRPC.

### Códigos de Estado

Mapea errores a códigos de estado gRPC:

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

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

Mapeos de códigos de estado comunes:

| Error de Goa | Código de estado gRPC | Caso de uso |
|-----------|-----------------|-----------|
| `not_found` | `CodeNotFound` | El recurso no existe |
| `invalid_argument` | `CodeInvalidArgument` | Entrada no válida |
| `internal_error` | `CodeInternal` | Error del servidor |
| `unauthenticated` | `CodeUnauthenticated` | Faltan credenciales o no son válidas |
| `permission_denied` | `CodePermissionDenied` | Permisos insuficientes |

### Definiciones de error

Definir errores a nivel de servicio o método:

```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)
        })
    })
})
```

### Devolución de errores

Utilizar constructores de error generados:

```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
}
```

---

## Implementación

### Implementación del servidor

```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)
}
```

### Implementación del cliente

```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)
}
```

---

## Integración del búfer de protocolo

### Generación automática

Goa genera automáticamente archivos `.proto` a partir de su diseño:

```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;
}
```

### Configuración Protoc

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

---

---

## Ver también

- [Referencia DSL: Streaming](dsl-reference/#streaming) - Patrones de streaming a nivel de diseño
- [Referencia DSL: Tratamiento de errores](dsl-reference/#tratamiento-de-errores-nivel-de-diseño) - Definiciones de errores a nivel de diseño
- [Guía HTTP](http-guide/) - Características del transporte HTTP
- [Guía de manejo de errores](error-handling/) - Patrones completos de manejo de errores
- [Documentación de Clue](../3-ecosystem/clue/) - Interceptores gRPC para observabilidad

---

## Mejores prácticas

### Tratamiento de errores
- Utilizar códigos de estado gRPC apropiados
- Incluir mensajes de error significativos
- Gestionar la cancelación del contexto y los tiempos de espera

### Streaming
- Mantener un tamaño razonable de los mensajes
- Implementar un control de flujo adecuado
- Establecer tiempos de espera adecuados
- Manejar EOF y errores con elegancia

### Rendimiento
- Utilizar tipos de campo adecuados
- Considerar el tamaño de los mensajes en el diseño
- Utilizar streaming para grandes conjuntos de datos

### Versionado
- Planificar la compatibilidad con versiones anteriores
- Utilice los números de campo de forma estratégica
- Considerar el versionado de paquetes

### Gestión de recursos
- Gestionar correctamente las conexiones gRPC
- Implementar el apagado graceful
- Limpiar los recursos al cancelar el contexto

