gRPC is a transport mechanism that sends and receives messages between two systems. Traditionally, these systems are a server and a client. As we described in the previous chapters, RPC can be implemented in Go for transferring JSON. We called it a JSON RPC service. Similarly, gRPC is specially designed to transfer data in the form of protocol buffers.
gRPC makes service creation easy and elegant. It provides a nice set of APIs that we can use to define services and start running them. In this section, we will focus on how to create a gRPC service and how to use it. The main advantage of gRPC is that it can be understood by multiple programming languages. Protocol buffers provide a common data structure. So, this combination enables seamless communication between various tech stacks and systems. This is the integral concept of distributed computing.
Square, Netflix, and many other giants leverage this gRPC to scale their huge traffic-prone services. Google uses gRPC heavily for their web services. We can leverage it to get better throughput between two internal services.
We need to install the grpc Go library and a protoc-gen plugin before writing the services. Install them using the following commands:
go get google.golang.org/grpc
go get -u github.com/golang/protobuf/protoc-gen-go
gRPC has the following benefits over a traditional HTTP/REST/JSON architecture:
- gRPC uses HTTP/2, which is a binary protocol.
- Header compression is possible in HTTP/2, which means less overhead.
- We can multiplex many requests on one connection.
- We can use protobufs for strict typing of data.
- Streaming requests or responses, instead of using a request/response transaction, is possible.
Take a look at the following diagram:
The preceding diagram clearly shows that any back-end system or mobile app can directly communicate to a gRPC server using a protocol buffer. Let's write a money transaction service in Go using gRPC and protocol buffers. A service in gRPC is an RPC contract. It takes a message and returns another message.
The steps for implementing the money transaction service are as follows:
- Create the protocol buffer with the definitions of service and messages.
- Compile the protocol buffer file.
- Use the generated Go package to create a gRPC server.
- Create a gRPC client that talks to the server.
To understand these steps, let's create the project directories for our upcoming example, like so:
mkdir -r $GOPATH/src/github.com/git-user/chapter6/grpcExample
mkdir -r $GOPATH/src/github.com/git-user/chapter6/grpcExample/protofiles
Create a file called transaction.proto for defining gRPC services:
touch -p $GOPATH/src/github.com/git-user/chapter6/grpcExample/protofiles/transaction.proto
Now, in the transaction.proto file, define the service and transaction messages, like this:
syntax = "proto3";
package protofiles;
message TransactionRequest {
string from = 1;
string to = 2;
float amount = 3;
}
message TransactionResponse {
bool confirmation = 1;
}
service MoneyTransaction {
rpc MakeTransaction(TransactionRequest) returns (TransactionResponse) {}
}
This is a simple protocol buffer for a money transaction on the server. We introduced the message keyword when we discussed protocol buffers. The new keyword, service, defines a gRPC service. This new keyword is solely related to gRPC, and the protoc-gen-go helper plugin translates it into an understandable format via the protoc compiler. Now, let's compile this file using protoc from the grpcExample directory:
protoc -I protofiles/ protofiles/transaction.proto --go_out=plugins=grpc:protofiles
This command is slightly bigger than the compile command we used previously. This is because we are using the protoc-gen-go plugin. This command simply says to use data files as the input directory for proto files and use the same directory for outputting the target Go files. Now, if we list the protofiles directory, we'll see an autogenerated file called transaction.pb.go:
ls protofiles
-rw-r--r-- 1 git-user staff 6215 Jan 16 17:28 transaction.pb.go
-rw-r--r-- 1 git-user staff 294 Jan 16 17:28 transaction.proto
Now, we have to build a server and client that consumes previously built protobufs. Create two more directories for the server and client logic in grpcExample, like this:
mkdir grpcServer grpcClient
Let's create a grPC server first. Add a file called server.go to the grpcServer directory, which implements the transaction service. Our goal is to create a server that collects a transaction request from the client and returns the confirmation.
We need the help of more packages here, that is, context and reflection. context is used to create a context variable, which lives throughout an RPC request's lifetime. Both of these libraries are used by gRPC for its internal functions:
import (
...
pb "github.com/git-user/chapter6/grpcExample/protofiles"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
If we open the autogenerated transaction.pb.go package in protofiles, we can clearly see that there are two important things:
- The MakeTransaction function, as part of the MoneyTransactionServer interface
- The RegisterMoneyTransactionServer function
MakeTransaction is used for implementing the service. Let's take a look at the implementation. It defines a struct and a method. This method performs the money transaction using the data that's supplied via the *pb.TransactionRequest argument:
// server is used to create MoneyTransactionServer.
type server struct{}
// MakeTransaction implements MoneyTransactionServer.MakeTransaction
func (s *server) MakeTransaction(ctx context.Context, in *pb.TransactionRequest) (*pb.TransactionResponse, error) {
// Use in.Amount, in.From, in.To and perform transaction logic
return &pb.TransactionResponse{Confirmation: true}, nil
}
MakeTransaction contains the RPC request details. It is basically a struct that maps to the TransactionRequest message we defined in the protocol buffer file. What's returned from MakeTransaction is TransactionResponse. This function signature matches with the one we defined in the protocol buffer file initially:
rpc MakeTransaction(TransactionRequest) returns (TransactionResponse) {}
Now comes the main block. Here, we have to create an instance of the gRPC server and register the server struct with it. We run this gRPC server on port 50051:
const (
port = ":50051"
)
func main() {
lis, err := net.Listen("tcp", port)
...
s := grpc.NewServer()
pb.RegisterMoneyTransactionServer(s, &server{})
reflection.Register(s)
if err := s.Serve(lis); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}
Now, we need to write a client. Add a file called client.go in the grpcClient directory. The client should dial the server and acquire a connection. Using that connection, we can call remote functions and get the results. A gRPC client also uses the same protobuf boilerplate classes so that it's in sync with the server. The following is the code for the client:
package main
import (
"log"
pb "github.com/git-user/chapter6/grpcExample/protofiles"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
const (
address = "localhost:50051"
)
func main() {
// Set up a connection to the server.
conn, err := grpc.Dial(address, grpc.WithInsecure())
...
// Create a client
c := pb.NewMoneyTransactionClient(conn)
from := "1234"
to := "5678"
amount := float32(1250.75)
// Make a server request.
r, err := c.MakeTransaction(context.Background(),
&pb.TransactionRequest{From: from,
To: to, Amount: amount})
...
}
This client is also using the grpc package. It uses an empty context called context.Background() to pass to the MakeTransaction function. The second argument of the function is the TransactionRequest struct:
&pb.TransactionRequest{From: from, To: to, Amount: amount}
Now, let's run both the server and the client and view the output. Open a new console and run the gRPC server by using the following command:
go run $GOPATH/src/github.com/git-user/chapter6/grpcExample/grpcServer/
server.go
The TCP server starts listening on port 50051. Now, open one more Terminal/shell and start the client program that talks to this server:
go run $GOPATH/src/github.com/git-user/chapter6/grpcExample/grpcClient/
client.go
It prints the output of the successful transaction:
2020/01/10 19:13:16 Transaction confirmed: true
At the same time, the server logs this message to the console:
2020/01/10 19:13:16 Amount: 1250.750000, From A/c:1234, To A/c:5678
Here, the client made a single request to the gRPC server and passed details of the From A/c number, the To A/c number, and Amount. The server picks those details, processes them, and sends a response saying everything is fine.
The full programs can be found in this chapter's project repository. In the next section, we'll look at bidirectional streaming in gRPC.