Blog of Maisnam Raju

Grpc from Scratch: Introduction and Proto Buffers

If you are reading this, you already know what gRPC is. But if you don’t here is a textbook answer. gRPC stands for Google Remote Procedure Call and runs on top of the HTTP/2 protocol, it offers some enhancements over traditional HTTP like header compression (not to be confused with gzip compression) which reduces the size of the payload and also speeds up performance.

gRPC also has another nice feature which is the Protocol Buffer; this allows developers to write a definition for their APIs and share it with other developers. It is language agnostic and supports code generation in multiple languages which makes it suitable for microservices. It also supports strong typing making it ideal for larger enterprise applications where developers are spread across multiple teams and need help communicating API specs faster. But the best feature of gRPC in my opinion is bidirectional streaming where the server and client can communicate with each other hence replacing traditional polling which means fewer resources wasted.

Proto/Protocol Buffers

A protocol buffer file or a .proto file serves as the API definition for a gRPC application.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
syntax = "proto3";

// Define the request and response message types

message UnaryRequest {
  string message = 1;
}

message UnaryResponse {
  string message = 1;
}

message StreamRequest {
  string message = 1;
}

message StreamResponse {
  string message = 1;
}

// Define the gRPC service

service MyService {
  // Unary RPC
  rpc UnaryExample(UnaryRequest) returns (UnaryResponse);

  // Server Streaming RPC
  rpc ServerStreamExample(UnaryRequest) returns (stream StreamResponse);

  // Client Streaming RPC
  rpc ClientStreamExample(stream StreamRequest) returns (UnaryResponse);

  // Bidirectional Streaming RPC
  rpc BidirectionalStreamExample(stream StreamRequest) returns (stream StreamResponse);
}

The syntax = "proto3"; defines the version of the proto being used. The current standard is at version 3 hence proto3 in the definition. Next, we have the message property. A message can be the request input or the output returned from a gRPC endpoint. It is the wrapper that contains all the properties that would be part of the input-output. It is divided into 3 parts

  1. datatype -gRPC supports a lot of different datatypes and here is a list of available datatypes.
  2. property name - the name of the field/property
  3. field number - this is pretty confusing but it is supposed to be like a unique labels for the different pieces of information within a message. They help in organizing and identifying each piece of data when you send or receive a message.

Different Types of datatypes Scalar Types:

  int32, int64: Signed 32-bit and 64-bit integers.
  uint32, uint64: Unsigned 32-bit and 64-bit integers.
  sint32, sint64: Signed zigzag-encoded 32-bit and 64-bit integers.
  fixed32, fixed64: 32-bit and 64-bit integers with fixed sizes.
  sfixed32, sfixed64: Signed 32-bit and 64-bit integers with fixed sizes.
  float: Single-precision floating-point numbers.
  double: Double-precision floating-point numbers.
  bool: Boolean values.
  string: A sequence of characters, represented as a UTF-8 encoded string.
  bytes: Binary data, often used for representing arbitrary binary content.
Enumeration Types: enum: An enumeration type represents a set of named integer values. Enums are used to define a list of possible values for a field.
syntax = "proto3";

// Define an enum called Color
enum Color {
  RED = 0;
  GREEN = 1;
  BLUE = 2;
  YELLOW = 3;
}

// Define a message that uses the Color enum
message PaintCan {
  string brand = 1;
  Color color = 2;
}

// Example usage
PaintCan myCan = {
  brand: "ABC Paint",
  color: GREEN
};
Repeated Fields: Fields of any scalar, enumeration, or message type can be repeated, representing arrays or lists of values.
syntax = "proto3";

message Person {
  string name = 1;
  repeated string email = 2;
}
Person myPerson = {
  name: "Alice",
  email: ["alice@example.com", "alice@gmail.com"]
};

syntax = "proto3";

// Define a message named "PhoneNumber" to represent phone numbers
message PhoneNumber {
  string number = 1;
  string type = 2;
}

// Define a message named "Person" with a repeated field of PhoneNumber objects
message Person {
  string name = 1;
  repeated PhoneNumber phone_numbers = 2;
}

// Example usage
Person myPerson = {
  name: "Alice"
  phone_numbers: [
    { number: "123-456-7890", type: "Home" },
    { number: "987-654-3210", type: "Work" }
  ]
};

Maps: Maps are another way to define slightly complicated data; in the definition below use define a nested object/dictionary with a key which is an int32 datatype and the value which is of the Employee datatype. NOTE: Messages also count as datatypes in gRPC

syntax = "proto3";

// Define a message named "Employee" to represent employee details, it includes a key, value pair 
message Employee {
  string name = 1;
  string department = 2;
  int32 employee_id = 3;
}

// Define a message named "Company" with a map field
message Company {
  string company_name = 1;
  map<int32, Employee> employees = 2;
}

// Example usage
Company myCompany = {
  company_name: "ACME Corporation"
  employees: {
    101: {
      name: "Alice",
      department: "HR",
      employee_id: 101
    }
    102: {
      name: "Bob",
      department: "Engineering",
      employee_id: 102
    }
  }
};
oneof: oneof datatypes can be used to define that a particular property will only return one of the properties mentioned in the definition.

syntax = "proto3";

message Transport {
  oneof vehicle {
    string car_model = 1;
    string bicycle_brand = 2;
    string public_transport = 3;
  }
}
The code above will return one of the following
{
  "car_model": "GTR"
}

{
 "bicycle_brand": "trek"
}

Timestamps: gRPC also supports a timestamp datatype but this needs to be imported from google’s proto buffer defintion using import "google/protobuf/timestamp.proto"; which brings us to the point that gRPC supports importing definitions.

Field Options: Field options can be used to define additional metadata for fields, such as whether a field is required, deprecated, or has custom serialization options.

There is still a lot more to learn about gRPC and this post only provides a starting point to the gRPC world. Hopefully I can keep adding more posts to help those who are starting out.