gRPC for Absolute Beginners, in Go
Tags: grpc, http2, protobuf
Published: 2022-05-28 15:43:57

## Introduction

The Internet has evolved in the last 2 decades a lot. HTTP/1.1 was not enough so we have HTTP2 now. The specification we used to transfer data between the client and the server has also evolved. From XML to JSON, now we have Protocol Buffer, which is a binary spec. Let's dive deeper.

The picture below from Wikipedia shows how exponential data is growing.

<img src="https://upload.wikimedia.org/wikipedia/commons/7/7c/Hilbert_InfoGrowth.png" alt="Growth of data">

## Prerequisites

- Go
- HTTP2

## Enough HTTP2 to know gRPC

During the early days of the web, we only had static content, hardly text files, and HTML files. After then, the web grew a bit and the web server and clients started to deal with rich media like rich texts, images, and videos. Servers also started to serve dynamic content based on what clients request.

This was the start of the RPC era. A client would hit a certain endpoint with certain data, and the server's work was to respond to that request. In the early time of RPC, XML was extensively used. We still use XML in some systems. But most of the world has moved to JSON as a communication format between the server and the client.

Now, over time, everything has evolved. One of the things which evolved is HTTP spec. Now we have HTTP2. HTTP2 is the base of gRPC. Certain properties make HTTP2 a nurturing ground for gRPC.

| HTTP/1.1                                    | HTTP/2                                     |
|---------------------------------------------|--------------------------------------------|
| Header is plaintext and not compressed      | Header is compressed and binary            |
| Spawns a new TCP connection on each request | uses already existing TCP connection       |
| TLS is not required                         | Requires TLS by default, enhanced security |


## Let's learn about Protocol Buffer first

We have seen how HTTP2 is essential for gRPC, now let's see where Protocol Buffer stands.

Although you can use gRPC with JSON, Protocol Buffer brings new things to the table. We saw that for a long time, JSON was used to send data back and forth between servers and clients. Now, what has happened is that we have taken yet another step to move from JSON to its successor.

Protocol buffers are building block of gRPC and is a replacement for JSON. Protocol Buffer inherits a lot from HTTP/2.

| JSON                     | Protocol Buffer                    |
|--------------------------|------------------------------------|
| Plaintext                | Binary                             |
| Larger payload over wire | Smaller payload over wire          |
| Write your server        | .proto files can create server stub|

- As we already know, HTTP/2 is binary. So are protocol buffers. Now you don't need to pass a date as a string in JSON. You can pass BSON over the wire.
- It is easy on a network as we only use the space we need to use. Say int32 only uses 4 bytes of data. The same data in JSON (string) could have used multiple bytes for a longer integer value.
- We write .proto files. the proto compiler generates stub files which can be used to write the server as well as the client. The key takeaway here is we can use the same proto files to generate clients and servers in many different languages.
- Protocol Buffers are agnostic to the language you are working with to develop your server or the client. .proto files have their syntax and data types which convert to specific data types in a destination programming language. This is my favorite reason to use gRPC in my projects.

I recommend you to read through the proto syntax, keywords, and data types here: <https://developers.google.com/protocol-buffers/docs/proto3>

## What is gRPC?

Ever heard of RPC? It stands for Remote Procedure Call. It's an old way of running a remote procedure on a remote machine. Let me make it a little familiar for you. When you hit an endpoint from a frontend to a backend, you are making a remote call or a remote procedure call. 

SOAP and REST both are an example of RPC. You can send data in the body and hit an API on the other end. That's how it has been happening since the start.

gRPC is a continuation of that SOAP and REST. gRPC brings all the advancements that their ancestors can't. With gRPC, a client application can directly call a method on a server application on a different machine as if it were a local object.

### Types of Service Methods in gRPC

We'll practically see what service methods are when we do hands-on with code. But right now I want to specify that in gRPC we have 4 kinds of method definitions.

<img src="https://d2kbulfv42d43j.cloudfront.net/2022/Q2/grpc-service-method-types.jpg" alt="Types of Service Methods">

1. **Unary** - Unary is similar to a normal REST call. A client initiates a TCP connection, sends a message, waits for the server to respond, and finally, the server responds.

2. **Server Streaming** - Server streaming RPCs where the client sends a request to the server and gets a stream to read a sequence of messages back. For example, you searched for a keyword. Instead of returning a static page, Twitter returns a stream of tweets, including whatever is being tweeted in real time.

3. **Client Streaming** - Client streaming RPCs where the client writes a sequence of messages and sends them to the server. Once the client has finished writing the messages, it waits for the server to read them and return its response. Again gRPC guarantees message ordering within an individual RPC call. An example of it would be an IoT device (e.g. a car) streaming its device location to the central server (e.g. Uber).

4. **Bidirectional Streaming** - Bidirectional streaming RPCs where both sides send a sequence of messages using a read-write stream. The two streams operate independently, so clients and servers can read and write in whatever order they like. An example of this would be a chat application. But I know there are more complex use cases available in the wild. Please let me know in the comments if you do so.

For a detailed explanation of what happens when a gRPC client calls a gRPC server method, please consider reading **[RPC life cycle](https://grpc.io/docs/what-is-grpc/core-concepts/#rpc-life-cycle)**.

## Developing with gRPC

We have had enough of theories. Let's develop some code to see this thing in action.

We are going to have an example in Go. Although as we already know, the proto files have a language of their own which is used to generate code in multiple languages.

We are going to write a simple calculator app.

### Write the proto file

`.proto` files are contracts between the server and the client. This is similar to the REST API you have already experienced with.

Before I proceed further, I'd like to inform you that having the whole code into a module will make your life easy. That's the reason, I've created a go module at the root of the directory named `github.com/santosh/example`.

#### calculator/calculator.proto

```proto
syntax = "proto3";

option go_package = "github.com/santosh/example/calculator";

package calculator;

// The calculator service definition.
service Calculator {
    // Adds two number
    rpc Add(Input) returns (Output);
}

// Input message containing two operands
message Input {
    int32 Operand1 = 1;
    int32 Operand2 = 2;
}

// Output message containing result of operation
message Output {
    int32 Result = 1;
}
```

### Explanation

Protocol buffer data is structured as messages, where each message is a small logical record of information containing a series of name-value pairs called fields. Line 7-10 is an example of a message. So is 12-14. Messages are comprised of data types, identifiers, and index positions.

You'd also note messages are composed inside Services. Service is simply what this web service does. We currently have a `Calculator` service from lines 3-5. Calculator service comprises of a method called `Add`. Add takes 2 parameters as defined in **Input** message and emits an **Output** message.

### Generate code from a proto file

You'd need a `protoc` compiler to generate code from proto files. If you are on Debian based system, you can use `sudo apt install protobuf-compiler`. If you are on any other OS, please read [Protocol Buffer Compiler Installation][1]

I am going to use Go for this tutorial, so I'm going to install the Go plugin for the protoc compiler.

```
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
```

Now with everything in place, I'd issue this command from the root of the module:

    protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative calculator/calculator.proto

If this command is successful, this would generate 2 more go files.

```
$ tree calculator
calculator
├── calculator_grpc.pb.go
├── calculator.pb.go
└── calculator.proto

0 directories, 3 files
```

If this is your first time with gRPC, and you look at the generated code now, you'd probably be lost.

We are going to use both files to create our server and the client. The generated code will start to make sense.

## Implement calculator server and the client

We all know that the output generated by the `protoc` compiler is a stub. We need to use that stub as a guide to implementing/writing our client and the server code. The stub work as a guideline for us.

Right now, the generated code lives inside `calculator` package in **calculator.pb.go** which mostly deals with data part (i.e. `Input`, `Output`, `Operand1`, `Operand2`, their getters etc) and **calculator_grpc.pb.go** which mostly deals with method implementation (i.e `Add` in both server and client). The first thing in both server and client code is to import this package.

I will keep referencing the proto file as we define our client/server code.

### Write gRPC calculator server

If you look into **calculator_grpc.pb.go**, you'd find that there is a struct called `UnimplementedCalculatorServer`. This struct represents our server. Now there is a reason it is named `Unimplemented`. If you look at methods attached to this struct, you'd see a method named `Add`. This is the same method we defined in our proto file. Here is a refresher:

    // Adds two number
    rpc Add(Input) returns (Output);

What we are going to do is we are going to take this `UnimplementedCalculatorServer` and implement the `Add` method.

```go
// server is used to implement calculator.CalculatorServer.
type server struct {
	pb.UnimplementedCalculatorServer
}

// Add implements calculator.CalculatorServer
func (s *server) Add(ctx context.Context, in *pb.Input) (*pb.Output, error) {
	log.Printf("Received: %v %v", in.GetOperand1(), in.GetOperand2())
	result := in.GetOperand1() + in.GetOperand2()
	return &pb.Output{Result: result}, nil
}
```

Pay attention to the signature of the method. We are taking `Input` in form of `*pb.Input` and returning `Output` in form of `*pb.Output`. This is very the same as we declared in the proto file.

The `Add` implementation is incomplete without Add logic. On line 26 we are using `GetOperand1` and `GetOperand2` which is available from the **calculator.pb.go** file. At last, we use the Output struct to return the result.

Implementing the method is not enough, we also need to start the gRPC server and start listening.


```go
func main() {
	flag.Parse()
	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterCalculatorServer(s, &server{})
	log.Printf("server listening at %v", lis.Addr())
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}
```

On line 32 I'm starting to listen to TCP connection on a given port. `grpc.NewServer()` calls the internal library function to create a new gRPC server, this call returns a `grpc.ServiceRegistrar` object. This object is then passed to `RegisterCalculatorServer` along with our implemented methods.

The whole code looks like this:

```go
package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"net"

	pb "github.com/santosh/example/calculator"
	"google.golang.org/grpc"
)

var (
	port = flag.Int("port", 50051, "The server port")
)

// server is used to implement calculator.CalculatorServer.
type server struct {
	pb.UnimplementedCalculatorServer
}

// Add implements calculator.CalculatorServer
func (s *server) Add(ctx context.Context, in *pb.Input) (*pb.Output, error) {
	log.Printf("Received: %v %v", in.GetOperand1(), in.GetOperand2())
	result := in.GetOperand1() + in.GetOperand2()
	return &pb.Output{Result: result}, nil
}

func main() {
	flag.Parse()
	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterCalculatorServer(s, &server{})
	log.Printf("server listening at %v", lis.Addr())
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}
```

In above implementation, we have used `flag` library to pass port from command line.


### Write gRPC calculator client

Like we have done with server code, we'll start by defining command line `flag`s.

```go
var (
	addr     = flag.String("addr", "localhost:50051", "the address to connect to")
	operand1 = flag.Int("op1", 2, "1st operand")
	operand2 = flag.Int("op2", 2, "2nd operand")

	operand1int32 = int32(*operand1)
	operand2int32 = int32(*operand2)
)
```

Line 19-20 here are kind of a hack as flag module does not have a way to accept `int32` which is requirement for our `Output.Result`.

Next is connecting with the server part:

```go
	// Set up a connection to the server.
	conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
```

`grpc.Dial` takes address of the server as well as other variadic parameters. As gRPC works over HTTP2 which is by default requires TLS, we are using insecure credentials as we have not configured our server to use certificates.

On the next line, we are going to create a new gRPC client:

```go
	c := pb.NewCalculatorClient(conn)
```

On preceding lines, we are going to invoke the `Add` endpoint:

```go
	// Contact the server and print out its response.
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	r, err := c.Add(ctx, &pb.Input{Operand1: operand1int32, Operand2: operand2int32})
	if err != nil {
		log.Fatalf("could not add: %v", err)
	}
	log.Printf("Add result: %v", r.GetResult())
```

The entirity of the code looks like this:

```go
package main

import (
	"context"
	"flag"
	"log"
	"time"

	pb "github.com/santosh/example/calculator"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

var (
	addr     = flag.String("addr", "localhost:50051", "the address to connect to")
	operand1 = flag.Int("op1", 2, "1st operand")
	operand2 = flag.Int("op2", 2, "2nd operand")

	operand1int32 = int32(*operand1)
	operand2int32 = int32(*operand2)
)

func main() {
	flag.Parse()
	// Set up a connection to the server.
	conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := pb.NewCalculatorClient(conn)

	// Contact the server and print out its response.
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	r, err := c.Add(ctx, &pb.Input{Operand1: operand1int32, Operand2: operand2int32})
	if err != nil {
		log.Fatalf("could not add: %v", err)
	}
	log.Printf("Add result: %v", r.GetResult())
}
```

Let's go ahead and test our service.

## Demo our server and the client

I have recorded a gif for the demo.

<img src="https://d2kbulfv42d43j.cloudfront.net/2022/Q2/grpc_calculator.gif" alt="gRPC calculator demo">

Here I have demonstrated with the default flag value, but you can override `-op1` and `-op2` flag to see different results.

## Conclusion

This was merely an introduction to gRPC and protocol buffer. What I've found is different than a conventional REST API is the endpoints. In REST, we have a predefined endpoint to hit. Such as `/calculation/add`. We would then pass JSON with the first and the second operand.

This is not the case with gRPC. We get the stub files as the `protoc` artifact. The only communication is from there.

In this post, I have only covered the **Unary** service method types. I'll leave streaming for some other time. If you'd like to read and practice more gRPC, I'd found [this series very helpful](https://dev.to/techschoolguru/series/7311).

### External Links

- [Protocol Buffers Proto3 documentation][2]
- [GRPC official Documentation][3]
- [What does multiplexing mean in HTTP/2][4]


[1]: https://grpc.io/docs/protoc-installation/
[2]: https://developers.google.com/protocol-buffers/docs/proto3
[3]: https://grpc.io/
[4]: https://stackoverflow.com/q/36517829/939986