Introduction to gRPC
by Michał Pawlik

code::dive 2019

A high performance, open-source universal RPC framework
An interprocess communication technique that is used for client-server based applications.
RPC
REST
JSON over HTTP/1.1
sounds similar to...
REpresentational State Transfer
Advantages
- Easy to learn
- Simple to implement
- Common
- Human readable serialization format
Drawbacks
- No documentation support built-in
- Requires boilerplate
- Bad support for versioning
- No type safety
- Inefficient format, CPU intensive compression
- No built-in support for pagination
- No valid way to trigger actions
Example OpenAPI definition
paths: /pet: post: tags: - "pet" summary: "Add a new pet to the store" description: "" operationId: "addPet" consumes: - "application/json" - "application/xml" produces: - "application/xml" - "application/json" parameters: - in: "body" name: "body" description: "Pet object that needs to be added to the store" required: true schema: $ref: "#/definitions/Pet" responses: 405: description: "Invalid input" security: - petstore_auth: - "write:pets" - "read:pets" put: tags: - "pet" summary: "Update an existing pet" description: "" operationId: "updatePet" consumes: - "application/json" - "application/xml" produces: - "application/xml" - "application/json" parameters: - in: "body" name: "body" description: "Pet object that needs to be added to the store" required: true schema: $ref: "#/definitions/Pet" responses: 400: description: "Invalid ID supplied" 404: description: "Pet not found" 405: description: "Validation exception" security: - petstore_auth: - "write:pets" - "read:pets"

Sample from https://editor.swagger.io/
gRPC
Alternative to REST?
We'll see...
Handful of facts
gRPC Remote Procedure Calls

gRPC background
- Free and open source since 2015
- Built by Google - successor to internal RPC platform called Stubby
- Project of CNCF (Cloud Native Computing Foundation)
- Adopted in production by Google, Salesforce, Netflix, Spotify and more
- Uses protocol buffers for serialization
gRPC principles
- Based on services and messages
- Cross platform
- Built-in streaming support
- Pluggable security, metrics, health checks using interceptors
- Versioning support
- Layered - key facets of the stack must be able to evolve independently
Compared to other RPCs
- Asynchronous, non-blocking - supports distributed systems
- Very simple to define - just define and compile protobuf
- Message-based, no shared or mutable state
- Open source, based on standards
gRPC communication
Protobuf
Protocol buffers are Google's language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler.
~developers.google.com
Let's try it out
Protobuf syntax
syntax = "proto3"; package example; message Person { string name = 1; int32 id = 2; string email = 3; } message Product { int64 id = 1; string name = 2; double price = 3; map<string,string> properties = 4; } message EmptyMessage {}
service ExampleService { rpc RequestResponse(MyRequestMsg) returns (MyResponseMsg) {} rpc ClientStreaming(stream MyRequestMsg) returns (MyResponseMsg) {} rpc ServerStreaming(MyRequestMsg) returns (stream MyResponseMsg) {} rpc BiDiStreaming(stream MyRequestMsgg) returns (stream MyResponseMsg) {} }
service ExampleService { rpc RequestResponse(MyRequestMsg) returns (MyResponseMsg) {} rpc ClientStreaming(stream MyRequestMsg) returns (MyResponseMsg) {} rpc ServerStreaming(MyRequestMsg) returns (stream MyResponseMsg) {} rpc BiDiStreaming(stream MyRequestMsgg) returns (stream MyResponseMsg) {} }
service ExampleService { rpc RequestResponse(MyRequestMsg) returns (MyResponseMsg) {} rpc ClientStreaming(stream MyRequestMsg) returns (MyResponseMsg) {} rpc ServerStreaming(MyRequestMsg) returns (stream MyResponseMsg) {} rpc BiDiStreaming(stream MyRequestMsgg) returns (stream MyResponseMsg) {} }
service ExampleService { rpc RequestResponse(MyRequestMsg) returns (MyResponseMsg) {} rpc ClientStreaming(stream MyRequestMsg) returns (MyResponseMsg) {} rpc ServerStreaming(MyRequestMsg) returns (stream MyResponseMsg) {} rpc BiDiStreaming(stream MyRequestMsgg) returns (stream MyResponseMsg) {} }
service ExampleService { rpc RequestResponse(MyRequestMsg) returns (MyResponseMsg) {} rpc ClientStreaming(stream MyRequestMsg) returns (MyResponseMsg) {} rpc ServerStreaming(MyRequestMsg) returns (stream MyResponseMsg) {} rpc BiDiStreaming(stream MyRequestMsgg) returns (stream MyResponseMsg) {} }
service ExampleService { rpc RequestResponse(MyRequestMsg) returns (MyResponseMsg) {} rpc ClientStreaming(stream MyRequestMsg) returns (MyResponseMsg) {} rpc ServerStreaming(MyRequestMsg) returns (stream MyResponseMsg) {} rpc BiDiStreaming(stream MyRequestMsgg) returns (stream MyResponseMsg) {} }
Message definition
Tag numbers
Service definition
Sample service
- Define a product
- Expose product listing method
Sample service
// file name: shop.proto syntax = "proto3"; package grpc_introduction; message Product { int64 id = 1; string name = 2; string description = 3; double price = 4; map<string,string> properties = 5; } message ProductsListRequest {} service ShopServer { rpc GetProducts (ProductsListRequest) returns (stream Product); }
// file name: shop.proto syntax = "proto3"; package grpc_introduction; message Product { int64 id = 1; string name = 2; string description = 3; double price = 4; map<string,string> properties = 5; } message ProductsListRequest {} service ShopServer { rpc GetProducts (ProductsListRequest) returns (stream Product); }
// file name: shop.proto syntax = "proto3"; package grpc_introduction; message Product { int64 id = 1; string name = 2; string description = 3; double price = 4; map<string,string> properties = 5; } message ProductsListRequest {} service ShopServer { rpc GetProducts (ProductsListRequest) returns (stream Product); }
// file name: shop.proto syntax = "proto3"; package grpc_introduction; message Product { int64 id = 1; string name = 2; string description = 3; double price = 4; map<string,string> properties = 5; } message ProductsListRequest {} service ShopServer { rpc GetProducts (ProductsListRequest) returns (stream Product); }
// file name: shop.proto syntax = "proto3"; package grpc_introduction; message Product { int64 id = 1; string name = 2; string description = 3; double price = 4; map<string,string> properties = 5; } message ProductsListRequest {} service ShopServer { rpc GetProducts (ProductsListRequest) returns (stream Product); }
// file name: shop.proto syntax = "proto3"; package grpc_introduction; message Product { int64 id = 1; string name = 2; string description = 3; double price = 4; map<string,string> properties = 5; } message ProductsListRequest {} service ShopServer { rpc GetProducts (ProductsListRequest) returns (stream Product); }
Implementation
Example in Go

Install dev tools
$ sudo dnf -y install protoc # or alike for your system
$ go get -u github.com/golang/protobuf/protoc-gen-go
Compile protobuf
$ protoc --go_out=. ./grpc_introduction/shop.proto
Import in code
package main import ( pb "grpc_introduction" "google.golang.org/grpc" ) /* Rest of the code */
Output
package grpc_introduction import ( fmt "fmt" proto "github.com/golang/protobuf/proto" math "math" ) /* ... skipped ... */ type Product struct { Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` Price float64 `protobuf:"fixed64,4,opt,name=price,proto3" json:"price,omitempty"` Properties map[string]string `protobuf:"bytes,5,rep,name=properties,proto3" json:"properties,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` }
Building server
type shopServer struct { products []*pb.Product } func (s *shopServer) registerProduct(p *pb.Product) { s.products = append(s.products, p) } func (s *shopServer) GetProducts(req *pb.ProductsListRequest, stream pb.ShopServer_GetProductsServer) error { for _, p := range s.products { if err := stream.Send(p); err != nil { return err } } log.Println("Products delivered") return nil } func main() { lis, _ := net.Listen("tcp", fmt.Sprintf(":%d", 9090)) log.Println("Listening on", lis.Addr()) grpcServer := grpc.NewServer() svr := &shopServer{} for i := 0; i < 20; i++ { p := pb.Product{ Name: fmt.Sprintf("Product-%d", i), Id: int64(i), } svr.registerProduct(&p) } pb.RegisterShopServerServer(grpcServer, svr) grpcServer.Serve(lis) }
type shopServer struct { products []*pb.Product } func (s *shopServer) registerProduct(p *pb.Product) { s.products = append(s.products, p) } func (s *shopServer) GetProducts(req *pb.ProductsListRequest, stream pb.ShopServer_GetProductsServer) error { for _, p := range s.products { if err := stream.Send(p); err != nil { return err } } log.Println("Products delivered") return nil } func main() { lis, _ := net.Listen("tcp", fmt.Sprintf(":%d", 9090)) log.Println("Listening on", lis.Addr()) grpcServer := grpc.NewServer() svr := &shopServer{} for i := 0; i < 20; i++ { p := pb.Product{ Name: fmt.Sprintf("Product-%d", i), Id: int64(i), } svr.registerProduct(&p) } pb.RegisterShopServerServer(grpcServer, svr) grpcServer.Serve(lis) }
type shopServer struct { products []*pb.Product } func (s *shopServer) registerProduct(p *pb.Product) { s.products = append(s.products, p) } func (s *shopServer) GetProducts(req *pb.ProductsListRequest, stream pb.ShopServer_GetProductsServer) error { for _, p := range s.products { if err := stream.Send(p); err != nil { return err } } log.Println("Products delivered") return nil } func main() { lis, _ := net.Listen("tcp", fmt.Sprintf(":%d", 9090)) log.Println("Listening on", lis.Addr()) grpcServer := grpc.NewServer() svr := &shopServer{} for i := 0; i < 20; i++ { p := pb.Product{ Name: fmt.Sprintf("Product-%d", i), Id: int64(i), } svr.registerProduct(&p) } pb.RegisterShopServerServer(grpcServer, svr) grpcServer.Serve(lis) }
type shopServer struct { products []*pb.Product } func (s *shopServer) registerProduct(p *pb.Product) { s.products = append(s.products, p) } func (s *shopServer) GetProducts(req *pb.ProductsListRequest, stream pb.ShopServer_GetProductsServer) error { for _, p := range s.products { if err := stream.Send(p); err != nil { return err } } log.Println("Products delivered") return nil } func main() { lis, _ := net.Listen("tcp", fmt.Sprintf(":%d", 9090)) log.Println("Listening on", lis.Addr()) grpcServer := grpc.NewServer() svr := &shopServer{} for i := 0; i < 20; i++ { p := pb.Product{ Name: fmt.Sprintf("Product-%d", i), Id: int64(i), } svr.registerProduct(&p) } pb.RegisterShopServerServer(grpcServer, svr) grpcServer.Serve(lis) }
type shopServer struct { products []*pb.Product } func (s *shopServer) registerProduct(p *pb.Product) { s.products = append(s.products, p) } func (s *shopServer) GetProducts(req *pb.ProductsListRequest, stream pb.ShopServer_GetProductsServer) error { for _, p := range s.products { if err := stream.Send(p); err != nil { return err } } log.Println("Products delivered") return nil } func main() { lis, _ := net.Listen("tcp", fmt.Sprintf(":%d", 9090)) log.Println("Listening on", lis.Addr()) grpcServer := grpc.NewServer() svr := &shopServer{} for i := 0; i < 20; i++ { p := pb.Product{ Name: fmt.Sprintf("Product-%d", i), Id: int64(i), } svr.registerProduct(&p) } pb.RegisterShopServerServer(grpcServer, svr) grpcServer.Serve(lis) }
type shopServer struct { products []*pb.Product } func (s *shopServer) registerProduct(p *pb.Product) { s.products = append(s.products, p) } func (s *shopServer) GetProducts(req *pb.ProductsListRequest, stream pb.ShopServer_GetProductsServer) error { for _, p := range s.products { if err := stream.Send(p); err != nil { return err } } log.Println("Products delivered") return nil } func main() { lis, _ := net.Listen("tcp", fmt.Sprintf(":%d", 9090)) log.Println("Listening on", lis.Addr()) grpcServer := grpc.NewServer() svr := &shopServer{} for i := 0; i < 20; i++ { p := pb.Product{ Name: fmt.Sprintf("Product-%d", i), Id: int64(i), } svr.registerProduct(&p) } pb.RegisterShopServerServer(grpcServer, svr) grpcServer.Serve(lis) }
type shopServer struct { products []*pb.Product } func (s *shopServer) registerProduct(p *pb.Product) { s.products = append(s.products, p) } func (s *shopServer) GetProducts(req *pb.ProductsListRequest, stream pb.ShopServer_GetProductsServer) error { for _, p := range s.products { if err := stream.Send(p); err != nil { return err } } log.Println("Products delivered") return nil } func main() { lis, _ := net.Listen("tcp", fmt.Sprintf(":%d", 9090)) log.Println("Listening on", lis.Addr()) grpcServer := grpc.NewServer() svr := &shopServer{} for i := 0; i < 20; i++ { p := pb.Product{ Name: fmt.Sprintf("Product-%d", i), Id: int64(i), } svr.registerProduct(&p) } pb.RegisterShopServerServer(grpcServer, svr) grpcServer.Serve(lis) }
type shopServer struct { products []*pb.Product } func (s *shopServer) registerProduct(p *pb.Product) { s.products = append(s.products, p) } func (s *shopServer) GetProducts(req *pb.ProductsListRequest, stream pb.ShopServer_GetProductsServer) error { for _, p := range s.products { if err := stream.Send(p); err != nil { return err } } log.Println("Products delivered") return nil } func main() { lis, _ := net.Listen("tcp", fmt.Sprintf(":%d", 9090)) log.Println("Listening on", lis.Addr()) grpcServer := grpc.NewServer() svr := &shopServer{} for i := 0; i < 20; i++ { p := pb.Product{ Name: fmt.Sprintf("Product-%d", i), Id: int64(i), } svr.registerProduct(&p) } pb.RegisterShopServerServer(grpcServer, svr) grpcServer.Serve(lis) }
Building client
func main() { conn, _ := grpc.Dial("localhost:9090", grpc.WithInsecure()) client := pb.NewShopServerClient(conn) ctx, _ := context.WithTimeout(context.Background(), 30*time.Second) stream, _ := client.GetProducts(ctx, &pb.ProductsListRequest{}) products := make([]*pb.Product, 0) for { product, err := stream.Recv() if err == io.EOF { break } if err != nil { log.Fatal(err) } products := append(products, product) } log.Println(products) }
func main() { conn, _ := grpc.Dial("localhost:9090", grpc.WithInsecure()) client := pb.NewShopServerClient(conn) ctx, _ := context.WithTimeout(context.Background(), 30*time.Second) stream, _ := client.GetProducts(ctx, &pb.ProductsListRequest{}) products := make([]*pb.Product, 0) for { product, err := stream.Recv() if err == io.EOF { break } if err != nil { log.Fatal(err) } products := append(products, product) } log.Println(products) }
func main() { conn, _ := grpc.Dial("localhost:9090", grpc.WithInsecure()) client := pb.NewShopServerClient(conn) ctx, _ := context.WithTimeout(context.Background(), 30*time.Second) stream, _ := client.GetProducts(ctx, &pb.ProductsListRequest{}) products := make([]*pb.Product, 0) for { product, err := stream.Recv() if err == io.EOF { break } if err != nil { log.Fatal(err) } products := append(products, product) } log.Println(products) }
func main() { conn, _ := grpc.Dial("localhost:9090", grpc.WithInsecure()) client := pb.NewShopServerClient(conn) ctx, _ := context.WithTimeout(context.Background(), 30*time.Second) stream, _ := client.GetProducts(ctx, &pb.ProductsListRequest{}) products := make([]*pb.Product, 0) for { product, err := stream.Recv() if err == io.EOF { break } if err != nil { log.Fatal(err) } products := append(products, product) } log.Println(products) }
func main() { conn, _ := grpc.Dial("localhost:9090", grpc.WithInsecure()) client := pb.NewShopServerClient(conn) ctx, _ := context.WithTimeout(context.Background(), 30*time.Second) stream, _ := client.GetProducts(ctx, &pb.ProductsListRequest{}) products := make([]*pb.Product, 0) for { product, err := stream.Recv() if err == io.EOF { break } if err != nil { log.Fatal(err) } products := append(products, product) } log.Println(products) }
func main() { conn, _ := grpc.Dial("localhost:9090", grpc.WithInsecure()) client := pb.NewShopServerClient(conn) ctx, _ := context.WithTimeout(context.Background(), 30*time.Second) stream, _ := client.GetProducts(ctx, &pb.ProductsListRequest{}) products := make([]*pb.Product, 0) for { product, err := stream.Recv() if err == io.EOF { break } if err != nil { log.Fatal(err) } products := append(products, product) } log.Println(products) }
Server build and run
$ go build
$ ./server
2019/09/17 11:06:36 Listening on [::]:9090
Client build and run
$ go build
$ ./client
2019/09/17 11:09:47 [name:"Product-0" id:1 name:"Product-1" id:2 name:"Product-2" id:3 name:"Product-3" id:4 name:"Product-4" id:5 name:"Product-5" id:6 name:"Product-6" id:7 name:"Product-7" id:8 name:"Product-8" id:9 name:"Product-9" id:10 name:"Product-10" id:11 name:"Product-11" id:12 name:"Product-12" id:13 name:"Product-13" id:14 name:"Product-14" id:15 name:"Product-15" id:16 name:"Product-16" id:17 name:"Product-17" id:18 name:"Product-18" id:19 name:"Product-19" ]
$ go build
$ ./server
2019/09/17 11:06:36 Listening on [::]:9090
2019/09/17 11:06:38 Products delivered
How about Python?

import grpc import shop_pb2 import shop_pb2_grpc channel = grpc.insecure_channel('localhost:9090') stub = shop_pb2_grpc.ShopServerStub(channel) products = stub.GetProducts(shop_pb2.ProductsListRequest()) for product in products: print(product.name, product.id)
import grpc import shop_pb2 import shop_pb2_grpc channel = grpc.insecure_channel('localhost:9090') stub = shop_pb2_grpc.ShopServerStub(channel) products = stub.GetProducts(shop_pb2.ProductsListRequest()) for product in products: print(product.name, product.id)
import grpc import shop_pb2 import shop_pb2_grpc channel = grpc.insecure_channel('localhost:9090') stub = shop_pb2_grpc.ShopServerStub(channel) products = stub.GetProducts(shop_pb2.ProductsListRequest()) for product in products: print(product.name, product.id)
import grpc import shop_pb2 import shop_pb2_grpc channel = grpc.insecure_channel('localhost:9090') stub = shop_pb2_grpc.ShopServerStub(channel) products = stub.GetProducts(shop_pb2.ProductsListRequest()) for product in products: print(product.name, product.id)
import grpc import shop_pb2 import shop_pb2_grpc channel = grpc.insecure_channel('localhost:9090') stub = shop_pb2_grpc.ShopServerStub(channel) products = stub.GetProducts(shop_pb2.ProductsListRequest()) for product in products: print(product.name, product.id)
import grpc import shop_pb2 import shop_pb2_grpc channel = grpc.insecure_channel('localhost:9090') stub = shop_pb2_grpc.ShopServerStub(channel) products = stub.GetProducts(shop_pb2.ProductsListRequest()) for product in products: print(product.name, product.id)
import grpc import shop_pb2 import shop_pb2_grpc channel = grpc.insecure_channel('localhost:9090') stub = shop_pb2_grpc.ShopServerStub(channel) products = stub.GetProducts(shop_pb2.ProductsListRequest()) for product in products: print(product.name, product.id)
Compile protobuf
$ mkdir python-client
$ cp shop.proto python-client
$ cd python-client
$ virtualenv -p python3 .venv
$ pip install grpcio grpcio-tools
$ python -m grpc_tools.protoc -I=. --python_out=. --grpc_python_out=. shop.proto
# shop_pb2_grpc.py and shop_pb2.py are now generated in current directory
Implement client
Run it!
$ python main.py
Product-0 0
Product-1 1
Product-2 2
Product-3 3
Product-4 4
Product-5 5
Product-6 6
Product-7 7
Product-8 8
Product-9 9
Product-10 10
Product-11 11
Product-12 12
Product-13 13
Product-14 14
Product-15 15
Product-16 16
Product-17 17
Product-18 18
Product-19 19
Other languages?
-
Supported officially
-
C++
-
Java
-
Python
-
Dart
-
Objective C
-
Go
-
Ruby
-
Node.js
-
Dart
-
C#
-
PHP
-
-
And many more community driven
Summing up
Benefits of gRPC
- Protocol first
- Type safety
- Polyglot
- Easy to bootstrap
- Compared to JSON:
- Smaller messages
- less CPU-intensive
Thank you!
Questions?
Introduction to gRPC by Michał Pawlik code::dive 2019
Introduction to gRPC - Code Dive
By Michał
Introduction to gRPC - Code Dive
- 459