gRPC

gRPC is a high-performance open-source framework that was developed at Google for remote procedure calls. It uses the Protocol Buffers (protobuf) serialization format by default and runs over HTTP/2 to support features like full-duplex streaming and efficient compression. Many microservices architectures choose gRPC for its speed and type safety, enabling strong contracts between service providers and consumers.

Architecture Overview

gRPC builds on the concept that a client can directly call methods on a server application as if it were a local object, with the communication details handled under the hood. It relies on protocol buffers to define data schemas (messages) and service endpoints (RPC methods). When a client calls a method, gRPC handles the transport details over HTTP/2, manages efficient binary serialization, and returns a strongly typed response.

+---------------------------+               +---------------------------+
|                           |               |                           |
|         gRPC Client       |               |         gRPC Server       |
|  (Desktop, Mobile, etc.)  |               | (Implements gRPC methods) |
+---------------------------+               +------------+--------------+
          |                                  ^           |
          | 1. Client calls gRPC method      |           |
          | (Unary, Streaming, etc.)         |           |
          v                                  |           |
+---------------------------+               |           |
|  HTTP/2 request with      |--------------->           |
|  protobuf-encoded data    |                            |
+---------------------------+                            |
                                                       |
        (Server processes request, prepares response)  |
                                                       |
+---------------------------+                            |
|  HTTP/2 response with     |<---------------------------|
|  protobuf-encoded data    |
+---------------------------+
          |
          | 2. Client decodes
          |    protobuf message
          v
+---------------------------+
|  Uses strong-typed data   |
|  in the application logic |
+---------------------------+

This diagram shows how the client calls a remote procedure, sends serialized data over HTTP/2, and receives a response in the same serialized format. The strong typing provided by protobuf ensures that both client and server agree on data structures before runtime.

Core Concepts

Protocol Buffers (Protobuf)

Protocol Buffers is a language-neutral, platform-neutral, extensible mechanism for serializing structured data. In a .proto file, developers define message types (data structures) and services (RPC methods). Code generation tools then produce client and server stubs in multiple languages based on these definitions.

Service Definition

A service definition describes the RPC methods that can be invoked. Each method specifies the request and response message types, along with the direction of data flow (unary or streaming). Here is a sample .proto file:

syntax = "proto3";
 
package bookstore;
 
// Message definitions
message Book {
  string id = 1;
  string title = 2;
  string author = 3;
}
 
message GetBookRequest {
  string id = 1;
}
 
message BookList {
  repeated Book books = 1;
}
 
// Service definition
service Bookstore {
  // Unary request/response
  rpc GetBook(GetBookRequest) returns (Book);
 
  // Server streaming
  rpc ListBooks(google.protobuf.Empty) returns (stream Book);
 
  // Client streaming
  rpc AddBooks(stream Book) returns (BookList);
 
  // Bidirectional streaming
  rpc Chat(stream Book) returns (stream Book);
}

This definition includes a Bookstore service with four RPC methods, demonstrating unary, server streaming, client streaming, and bidirectional streaming calls.

Communication Modes

gRPC supports four primary communication patterns:

  • Unary RPC. The client sends a single request and receives a single response.
  • Server streaming RPC. The client sends a single request; the server sends back a stream of responses.
  • Client streaming RPC. The client sends a stream of requests; the server responds once the entire client stream is complete.
  • Bidirectional streaming RPC. Both client and server simultaneously read and write in a stream until they have finished.

Code Generation

After defining your .proto file, use protoc with gRPC plugins to generate language-specific stubs. The following table summarizes some common protoc flags:

FlagDescription
--proto_pathSpecifies directories in which to search for imports.
--go_out, --java_outGenerates language-specific source files (for instance, Go, Java).
--go-grpc_outGenerates gRPC service interfaces in Go.
--grpc-java_outGenerates gRPC service stubs for Java.
--pluginAllows specifying the path to the gRPC plugin for protoc.

As an example, running protoc in Go might look like this:

protoc --proto_path=. --go_out=. --go-grpc_out=. bookstore.proto

This command generates Go files containing message types and service interfaces you can implement on your server, and client stubs you can use to invoke RPC methods.

Example Implementation in Go

Here is a simplified snippet showing how you might implement a Bookstore service in Go.

package main
 
import (
    "context"
    "fmt"
    "net"
 
    "google.golang.org/grpc"
    pb "path/to/generated/bookstore"
)
 
type bookstoreServer struct {
    pb.UnimplementedBookstoreServer
}
 
func (s *bookstoreServer) GetBook(ctx context.Context, req *pb.GetBookRequest) (*pb.Book, error) {
    // For demonstration, returns a static book.
    return &pb.Book{
        Id:     req.GetId(),
        Title:  "A Sample Book",
        Author: "An Author",
    }, nil
}
 
func main() {
    listener, err := net.Listen("tcp", ":50051")
    if err != nil {
        fmt.Println("Failed to listen:", err)
        return
    }
 
    grpcServer := grpc.NewServer()
    pb.RegisterBookstoreServer(grpcServer, &bookstoreServer{})
 
    fmt.Println("gRPC server listening on port 50051...")
    if err := grpcServer.Serve(listener); err != nil {
        fmt.Println("Failed to serve:", err)
    }
}

In this example, bookstoreServer provides a method GetBook that satisfies the interface generated from the .proto file. After creating a TCP listener on port 50051, you create a gRPC server and register the Bookstore service implementation.

Working with gRPC on the Command Line

Clients typically connect using generated stubs in the same language as the server, but grpcurl is a popular CLI tool for testing gRPC services without needing to write code. Below is a demonstration of how to use grpcurl to call the Bookstore service.

grpcurl -plaintext -d '{"id":"123"}' localhost:50051 bookstore.Bookstore/GetBook
  • Example output:
    {
      "id": "123",
      "title": "A Sample Book",
      "author": "An Author"
    }
  • Interpretation of the output:
    The response shows the book data retrieved from the server. The JSON matches the message defined in the .proto file. The -plaintext option is used for an unencrypted connection (often acceptable for local testing). The -d flag sends the request body, which is converted internally to protobuf.

Performance Characteristics

A big reason many teams pick gRPC is speed. gRPC uses HTTP/2 for multiplexing multiple messages over a single connection, supporting flow control and efficient streaming. A simple concurrency formula for server capacity might be written as

Server capacity = total_threads / average_call_duration

When calls are short and concurrency is managed well, gRPC servers can handle many simultaneous requests. Streaming helps when large datasets must be transferred in chunks without blocking the entire pipeline.

Best Practices

Consider a few approaches when designing and operating gRPC services:

  • Keep .proto definitions organized, preferably with smaller files that each handle a logical domain.
  • Employ secure connections by using TLS in production environments to encrypt traffic on HTTP/2.
  • Use deadlines or timeouts to prevent client calls from hanging indefinitely.
  • Rely on load balancing strategies that are compatible with HTTP/2 and maintain persistent connections.
  • Monitor service performance by collecting metrics on request latencies, error rates, and resource usage.