Introduction
Implementing a gRPC server in Go involves generating code from your .proto file, embedding the unimplemented server struct for forward compatibility, and registering your implementation with the gRPC server.
Key Concepts
- UnimplementedServer: A generated struct you embed in your server type. It provides default implementations for all methods, ensuring forward compatibility when new methods are added to the proto.
- protoc code generation: The
protoccompiler with Go plugins generates interfaces, message types, and registration functions from.protofiles. - grpc.NewServer(): Creates a new gRPC server instance that listens on a TCP port.
- Streaming RPCs: Server-side streaming sends multiple responses; client-side streaming receives multiple requests.
Real World Context
In a microservice architecture, each service implements one or more gRPC servers. Embedding UnimplementedServer is not optional — it prevents your service from crashing when a client calls a method you have not implemented yet, which happens during rolling deployments.
Deep Dive
Generate Go code from your .proto file.
bashprotoc --go_out=. --go-grpc_out=. greeter.proto
This generates message types and a service interface.
Implement the server by embedding the unimplemented server and overriding the methods you need.
gotype server struct { pb.UnimplementedGreeterServer } func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) { return &pb.HelloReply{ Message: "Hello, " + req.Name, }, nil } func main() { lis, err := net.Listen("tcp", ":50051") if err != nil { log.Fatal(err) } s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) log.Println("gRPC server listening on :50051") if err := s.Serve(lis); err != nil { log.Fatal(err) } }
The Serve call blocks and handles incoming connections.
gRPC supports four communication patterns:
- Unary: Request-response (most common).
- Server Streaming: Server sends multiple responses.
- Client Streaming: Client sends multiple requests.
- Bidirectional: Both stream simultaneously.
Common Pitfalls
- Not embedding
UnimplementedServer— Without it, adding a new method to the proto requires updating all servers simultaneously, or they will fail to compile. - Forgetting to call
s.GracefulStop()on shutdown — Without graceful shutdown, in-flight RPCs are abruptly terminated.
Best Practices
- Always embed
UnimplementedServer— This is required for forward compatibility and is enforced by the gRPC Go code generator. - Use
s.GracefulStop()for shutdown — Just like HTTP graceful shutdown, this waits for in-flight RPCs to complete.
Summary
- Generate Go code from
.protofiles usingprotocwith Go plugins. - Embed
UnimplementedServerin your implementation for forward compatibility. - gRPC supports unary, server streaming, client streaming, and bidirectional streaming.
Code Examples
func (s *server) ListUsers(req *pb.ListRequest, stream pb.Users_ListUsersServer) error {
for _, user := range users {
if err := stream.Send(&pb.User{Id: user.ID, Name: user.Name}); err != nil {
return err
}
}
return nil
}