Introduction to gRPC

gRPC is an open-source Remote Procedure Call (RPC) framework developed by Google. It uses HTTP/2 as the transport layer and Protocol Buffers (protobuf) as the interface description language and serialisation format. Clients call methods on remote servers as if they were local function calls; the framework handles serialisation, transport, and error propagation.

gRPC is built on HTTP/2, which means it gets multiplexing, header compression, and bidirectional streaming for free. This makes it significantly more efficient than REST/JSON over HTTP/1.1 for high-throughput or latency-sensitive service communication.

How gRPC fits together

graph LR
  proto[".proto file"]
  gen["grpc_tools_ruby_protoc\n(code generator)"]
  pb["greeter_pb.rb\n(message classes)"]
  svc["greeter_services_pb.rb\n(stub + skeleton)"]
  client["Ruby client\n(uses Stub)"]
  server["Ruby server\n(implements Service)"]
  http2["HTTP/2 transport"]

  proto --> gen
  gen --> pb
  gen --> svc
  pb --> client
  pb --> server
  svc --> client
  svc --> server
  client -->|"RPC call"| http2
  http2 -->|"dispatched method"| server

The .proto file is the single source of truth. Both the client and server are generated from it, ensuring they share the same message shapes and method signatures at compile time.

Why gRPC over REST

  gRPC REST/JSON
Schema Strongly typed .proto Optional (OpenAPI)
Serialisation Binary protobuf Text JSON
Transport HTTP/2 HTTP/1.1 or HTTP/2
Streaming Native (4 modes) Limited (SSE, WebSocket)
Code generation Built-in Third-party tools
Browser support Requires gRPC-Web proxy Native

gRPC is a natural fit for internal service-to-service communication, microservices, and any scenario where you control both ends of the wire. REST remains the better choice for public-facing APIs consumed directly by browsers.

Protocol Buffers

Protocol Buffers (protobuf) is Google’s language-neutral, platform-neutral, extensible mechanism for serialising structured data. A .proto file defines:

  • Messages structured data types equivalent to classes or structs.
  • Services a named set of RPC methods, each with a request and response message.
  • Fields typed, numbered members of a message; field numbers identify fields in the binary encoding.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
syntax = "proto3";

package greeter;

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

Field numbers must never be reused once a message is in production. Changing a field number is a breaking change because the binary encoding uses numbers, not names, to identify fields.

Scalar types

Proto type Ruby type Notes
string String UTF-8 encoded
int32 / int64 Integer Signed
uint32 / uint64 Integer Unsigned
bool TrueClass / FalseClass  
float / double Float  
bytes String Binary data

Service types

gRPC supports four communication patterns, all defined in the .proto file:

Type Proto syntax Description
Unary rpc Method (Req) returns (Res) One request, one response. Equivalent to a standard HTTP call.
Server streaming rpc Method (Req) returns (stream Res) One request; server sends a stream of responses.
Client streaming rpc Method (stream Req) returns (Res) Client sends a stream of requests; server replies once.
Bidirectional streaming rpc Method (stream Req) returns (stream Res) Both sides stream independently over a single connection.

Ruby setup

Add the gems to your Gemfile:

1
2
gem 'grpc'
gem 'grpc-tools'

Install dependencies:

1
bundle install

The grpc gem provides the runtime (client stubs, server, channels, interceptors). The grpc-tools gem bundles the grpc_tools_ruby_protoc binary, which wraps protoc and the Ruby gRPC plugin so no separate protoc installation is required.

Generating Ruby code

Given greeter.proto in the project root, generate the Ruby files into lib/:

1
2
3
4
grpc_tools_ruby_protoc \
  --ruby_out=lib \
  --grpc_out=lib \
  greeter.proto

This produces two files:

File Contents
lib/greeter_pb.rb Ruby classes for HelloRequest and HelloReply
lib/greeter_services_pb.rb Greeter::Service skeleton and Greeter::Stub client class

Regenerate these files every time greeter.proto changes. Never edit the generated files by hand; any hand-edit is overwritten on the next generation.

Implementing the server

Subclass the generated Greeter::Service and implement each RPC method. The method receives the request object and a GRPC::ActiveCall::SingleReqView call object, and returns the response.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
require 'grpc'
require_relative '../lib/greeter_services_pb'

class GreeterServer < Greeter::Service
  def say_hello(hello_req, _unused_call)
    Greeter::HelloReply.new(message: "Hello, #{hello_req.name}!")
  end
end

def main
  server = GRPC::RpcServer.new
  server.add_http2_port('0.0.0.0:50051', :this_port_is_insecure)
  server.handle(GreeterServer)
  puts 'gRPC server running on port 50051'
  server.run_till_terminated_or_interrupted([1, 'int', 'SIGTERM'])
end

main

:this_port_is_insecure disables TLS for local development. In production, pass a GRPC::Core::ServerCredentials object with your certificate and key.

Implementing the client

Create a stub using the generated Greeter::Stub class, then call the RPC method directly.

1
2
3
4
5
6
7
8
9
10
11
12
13
require 'grpc'
require_relative '../lib/greeter_services_pb'

def main
  stub = Greeter::Stub.new('localhost:50051', :this_channel_is_insecure)

  request = Greeter::HelloRequest.new(name: 'Alice')
  response = stub.say_hello(request)

  puts "Server replied: #{response.message}"
end

main

Run the server in one terminal and the client in another:

1
ruby server.rb
1
2
ruby client.rb
# => Server replied: Hello, Alice!

Server streaming example

For a server-streaming RPC the client calls the method and iterates over the responses using each:

Add the streaming method to the proto:

1
2
3
4
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
  rpc SayHelloManyTimes (HelloRequest) returns (stream HelloReply);
}

Server implementation:

1
2
3
4
5
def say_hello_many_times(hello_req, _unused_call)
  5.times.lazy.map do |i|
    Greeter::HelloReply.new(message: "Hello #{hello_req.name}, reply #{i + 1}")
  end
end

Client consumption:

1
2
responses = stub.say_hello_many_times(Greeter::HelloRequest.new(name: 'Bob'))
responses.each { |reply| puts reply.message }

The server returns a lazy enumerator; the grpc runtime streams each element to the client as it is produced.

What to avoid

Do not edit generated files. greeter_pb.rb and greeter_services_pb.rb are overwritten every time you run grpc_tools_ruby_protoc. All business logic belongs in your own classes that inherit from or wrap the generated skeleton.

Do not use HTTP/1.1 load balancers in front of gRPC servers. gRPC requires HTTP/2, which uses a single persistent connection with multiplexed streams. HTTP/1.1 load balancers treat each stream as a connection and cannot balance traffic across multiple server instances. Use an HTTP/2-capable load balancer (AWS ALB with gRPC routing, Envoy, or a Kubernetes Gateway API implementation).

Do not skip deadlines on client calls. Without a deadline, a client call can block indefinitely if the server is slow or unresponsive. Set a deadline on every call:

1
stub.say_hello(request, deadline: Time.now + 5)

Do not reuse field numbers in .proto files. Once a field number has been used in a production message, removing or reassigning it breaks binary compatibility with older clients and servers still using that number. Mark removed fields with reserved instead.

Do not share a single stub across many goroutines or threads without understanding connection limits. The grpc Ruby gem manages a connection pool internally, but a single channel has a maximum number of concurrent streams (default 100 for HTTP/2). For high-concurrency workloads, benchmark your pool size and consider multiple channels or connection management at a higher level.