gRPC es el framework de llamadas a procedimiento remoto de Google y lleva Protocol Buffers como lenguaje de definición de interfaces. La combinación da un contrato tipado, transporte HTTP/2 y generación de código para el servidor y el cliente. Aquí se explica cómo montarlo de cero en Go.
Instalación de protoc y los plugins Go
Necesitas el compilador de Protocol Buffers (protoc) y dos plugins: uno para generar los mensajes Go y otro para generar el código gRPC:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
En Linux puedes instalar protoc con el gestor de paquetes de tu distribución o descargarlo desde el repositorio oficial de protocolbuffers. Asegúrate de que tanto protoc como los dos plugins estén en el PATH antes de continuar.
Definir el servicio en .proto
El fichero .proto describe los mensajes y los métodos que expone el servicio. Es el único punto de verdad que comparten cliente y servidor:
syntax = "proto3";
option go_package = "github.com/tuusuario/miservicio/proto;proto";
package greeter;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
rpc GetUser (UserRequest) returns (UserReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
message UserRequest {
int32 id = 1;
}
message UserReply {
int32 id = 1;
string name = 2;
string email = 3;
}
Generar el código Go
Con el fichero guardado como proto/greeter.proto, ejecuta:
protoc --go_out=. --go_opt=paths=source_relative
--go-grpc_out=. --go-grpc_opt=paths=source_relative
proto/greeter.proto
Obtendrás dos ficheros: greeter.pb.go con los structs de mensajes y greeter_grpc.pb.go con las interfaces del servidor y el cliente stub. No edites esos ficheros a mano.
Implementar el servidor
El servidor tiene que implementar la interfaz GreeterServer generada. El patrón recomendado es embeber UnimplementedGreeterServer para que los métodos no implementados devuelvan un error gRPC correcto en lugar de un panic:
package main
import (
"context"
"fmt"
"log"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
pb "github.com/tuusuario/miservicio/proto"
)
type server struct {
pb.UnimplementedGreeterServer
}
func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
if req.Name == "" {
return nil, status.Error(codes.InvalidArgument, "el nombre no puede estar vacío")
}
return &pb.HelloReply{Message: "Hola, " + req.Name}, nil
}
func (s *server) GetUser(ctx context.Context, req *pb.UserRequest) (*pb.UserReply, error) {
// simulación: en producción aquí va la consulta a BBDD
usuarios := map[int32]*pb.UserReply{
1: {Id: 1, Name: "Ana García", Email: "[email protected]"},
2: {Id: 2, Name: "Luis Martín", Email: "[email protected]"},
}
u, ok := usuarios[req.Id]
if !ok {
return nil, status.Errorf(codes.NotFound, "usuario %d no encontrado", req.Id)
}
return u, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("error al escuchar: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
fmt.Println("Servidor gRPC en :50051")
if err := s.Serve(lis); err != nil {
log.Fatalf("error al servir: %v", err)
}
}
Implementar el cliente
package main
import (
"context"
"fmt"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "github.com/tuusuario/miservicio/proto"
)
func main() {
conn, err := grpc.NewClient("localhost:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
log.Fatalf("no se pudo conectar: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
reply, err := c.SayHello(ctx, &pb.HelloRequest{Name: "María"})
if err != nil {
log.Fatalf("error en SayHello: %v", err)
}
fmt.Println(reply.Message)
user, err := c.GetUser(ctx, &pb.UserRequest{Id: 1})
if err != nil {
log.Fatalf("error en GetUser: %v", err)
}
fmt.Printf("Usuario: %s (%s)n", user.Name, user.Email)
}
Manejo de errores tipados
Los errores gRPC llevan un código de estado que el cliente puede inspeccionar para decidir qué hacer. El paquete google.golang.org/grpc/status lo gestiona:
import "google.golang.org/grpc/status"
import "google.golang.org/grpc/codes"
user, err := c.GetUser(ctx, &pb.UserRequest{Id: 999})
if err != nil {
st, ok := status.FromError(err)
if ok && st.Code() == codes.NotFound {
fmt.Println("usuario no encontrado, creándolo...")
} else {
log.Fatalf("error inesperado: %v", err)
}
}
Interceptores de logging
Los interceptores son el equivalente gRPC del middleware HTTP. Se registran al crear el servidor y se ejecutan en cada llamada:
func logInterceptor(
ctx context.Context,
req any,
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (any, error) {
start := time.Now()
resp, err := handler(ctx, req)
log.Printf("[gRPC] %s | duración=%s | error=%v",
info.FullMethod, time.Since(start), err)
return resp, err
}
s := grpc.NewServer(grpc.UnaryInterceptor(logInterceptor))
Para combinar varios interceptores sin importar librerías externas puedes encadenarlos manualmente, aunque paquetes como go-grpc-middleware facilitan la composición con grpc.ChainUnaryInterceptor.
