What is Remote Procedure Call (RPC)

Remote Procedure Call is a software communication protocol that one program can use to request a service from a program located in another computer on a network without having to understand the network’s details. RPC is used to call other processes on the remote systems like a local system. A procedure call is also sometimes known as a function call or a subroutine call.

RPC uses the client-server model. The requesting program is a client, and the service-providing program is the server. Like a local procedure call, an RPC is a synchronous operation requiring the requesting program to be suspended until the results of the remote procedure are returned. However, the use of lightweight processes or threads that share the same address space enables multiple RPCs to be performed concurrently.

What is gRPC

gRPC (the “g” stands for something different in every gRPC release) is an inter-process communication technology that allows you to connect, invoke, operate, and debug distributed heterogeneous applications as easily as making a local function call.
When you develop a gRPC application the first thing that you do is define a service interface. The service interface definition contains information on how your service can be consumed by consumers, what methods you allow the consumers to call remotely, what method parameters and message formats to use when invoking those methods, and so on. The language that we specify in the service definition is known as an interface definition language (IDL).

tranhuy_microservice and a consumer based on gRPC
(microservice and a consumer based on gRPC)

gRPC uses protocol buffers as the IDL to define the service interface
So

What is protocol buffers

According to google dev page, Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.

Using gRPC for Microservices Communication

One of the main usages of gRPC is to implement microservices and their inter-service communication. In microservices inter-service communication, gRPC is used along with other communication protocols and usually gRPC services are implemented as polyglot services (implemented with different programming languages). To understand this further, let’s take a real-world scenario of an online retail system, which is an extended version of what we have discussed so far.
In this scenario, we have a number of microservices serving specific business capabilities of the online relation system. There are services such as the Product service, which is implemented as a gRPC service, and there are composite services such as the Catalog service, which calls multiple downstream services to build its business capability.

For most of the synchronous message passing scenarios, we can use gRPC. When you have certain asynchronous messaging scenarios that may require persistent messaging, then you can use event brokers or message brokers, such as Kafka, Active MQ, RabbitMQ, and NATS. When you have to expose certain business functionalities to the external world, then you can use the conventional REST/OpenAPI-based services or the GraphQL service. Thus services such as Catalog and Checkout are consuming gRPC-based backend services, and also exposing RESTful or GraphQL-based external-facing interfaces

Tranhuy_A common microservices deployment pattern with gRPC and other protocols
(A common microservices deployment pattern with gRPC and other protocols)

gRPC: Creating the Service Definition

When you develop a gRPC application, the first thing you do is define the service interface, which contains the methods that allow consumers to call remotely, the method parameters and message formats to use when invoking those methods, and so on. All these service definitions are recorded as a protocol buffer’s definition, which is the interface definition language (IDL) used in gRPC.

gRPC: Defining Messages

The message is the data structure that is exchanged between client and service. As you can see in picture below
tranhuy_Client–server interaction of ProductInfo service
(Client–server interaction of ProductInfo service)

Our ProductInfo use case has two message types. One is the product information (Product), which is required when adding a new product to the system and is returned when retrieving a particular product. The other is a unique identification (ProductID) of the product, which is required when retrieving a particular product from the system and is returned when adding a new product:

ProductID is a unique identifier of the product that can be a string value. We can either define our own message type that contains a string field or use the well-known message type google.protobuf.StringValue, provided by the protocol buffer library. In this example, we are going to define our own message type that contains a string field. The ProductID message type definition is shown in Example

Example Code: Protocol Buffer definition of ProductID message type.
message ProductID {
string value = 1;
}

Example Code: Protocol buffer definition of Product message type
message Product {
string id = 1;
string name = 2;
string description = 3;
float price = 4;
}

gRPC: Defining Services

A service is a collection of remote methods that are exposed to a client. In our sample, the ProductInfo service has two remote methods: addProduct(product) and getProduct(productId). According to the protocol buffer rule, we can only have one input parameter in a remote method and it can return only one value. If we need to pass multiple values to the method like in the addProduct method, we need to define a message type and group all the values as we have done in the Product message type:
addProduct

Creates a new Product in the system. It requires the details of the product as input and returns the product identification number of the newly added product, if the action completed successfully.

Example below shows the definition of the addProduct method.
Example Code: Protocol buffer definition of addProduct method
rpc addProduct(Product) returns (google.protobuf.StringValue);
getProduct

Retrieves product information. It requires the ProductID as input and returns Product details if a particular product exists in the system.

Example below shows the definition of the getProduct method.
Example Code: Protocol buffer definition of getProduct method

rpc getProduct(google.protobuf.StringValue) returns (Product);

gRPC: Combine Messages and Services

Combining all messages and services, we now have a complete protocol buffer definition for our ProductInfo use case, as shown in Example below

Example code: gRPC service definition of ProductInfo service using protocol buffers
syntax = “proto3”;
package ecommerce;
service ProductInfo {
rpc addProduct(Product) returns (ProductID);
rpc getProduct(ProductID) returns (Product);
}

message Product {
string id = 1;
string name = 2;
string description = 3;
}

message ProductID {
string value = 1;
}

gRPC: Implementing a gRPC service with Go

Implementing the Go service has three steps. First, we need to generate the stubs for the service definition, then we implement the business logic of all the remote methods of the service, and finally, we create a server listening on a specified port and register the service to accept client requests.

gRPC: Developing a gRPC Server

To create the server in Go, let’s create a new Go file named main.go inside the same Go package (productinfo/service) and implement the main method as shown in Example below
Example code: gRPC server implementation to host ProductInfo service in Go
package main

import (
“log”
“net”

pb “productinfo/service/ecommerce”
“google.golang.org/grpc”
)

const (
port = “:50051”
)

func main() {
lis, err := net.Listen(“tcp”, port)
if err != nil {
log.Fatalf(“failed to listen: %v“err)
}
s := grpc.NewServer()
pb.RegisterProductInfoServer(s, &server{})

log.Printf(“Starting gRPC listener on port ” + port)
if err := s.Serve(lis); err != nil {
log.Fatalf(“failed to serve: %v”, err)
}
}

Import the package that contains the generated code we just created from the protobuf compiler.

TCP listener that we want the gRPC server to bind to is created on the port (50051).

New gRPC server instance is created by calling gRPC Go APIs.

The service implemented earlier is registered to the newly created gRPC server by calling generated APIs.

Start listening to the incoming messages on the port (50051).
Now we have completed building a gRPC service for our business use case in the Go language.

gRPC: Developing a gRPC Client with Go

Let’s start by creating a new Go module (productinfo/client) and subdirectory (ecommerce) inside the module. In order to implement the Go client application, we

“also need to generate the stub as we have done when implementing the Go service. Since we need to create the same file (product_info.pb.go) and need to follow the same steps to generate the stubs, we are not going to mention it here. Please refer to “Generating client/server stubs” to generate stub files.
Let’s create a new Go file named productinfo_client.go inside the Go module (productinfo/client) and implement the main method to invoke remote methods as shown in Example below.

Example code: gRPC client application in Go
package main

import (
“context”
“log”
“time”

pb “productinfo/client/ecommerce”
“google.golang.org/grpc”

)

const (
address = “localhost:50051”
)

func main() {

conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf(“did not connect: %v”, err)
}
defer conn.Close()
c := pb.NewProductInfoClient(conn)

name := “Apple iPhone 11”
description := `Meet Apple iPhone 11. All-new dual-camera system with
Ultra Wide and Night mode.`
price := float32(1000.0)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.AddProduct(ctx,
&pb.Product{Name: name, Description: description, Price: price})
if err != nil {
log.Fatalf(“Could not add product: %v”, err)
}
log.Printf(“Product ID: %s added successfully”, r.Value)

product, err := c.GetProduct(ctx, &pb.ProductID{Value: r.Value})
if err != nil {
log.Fatalf(“Could not get product: %v”, err)
}
log.Printf(“Product: “, product.String())

}

Import the package that contains the generated code we created from the
protobuf compiler.

Set up a connection with the server from the provided address (“localhost:50051”). Here we create an unsecured connection between client and server.

Pass the connection and create a stub. This stub instance contains all the remote methods to invoke the server.

Create a Context to pass with the remote call. Here the Context object contains metadata such as the identity of the end user, authorization tokens, and the request’s deadline and it will exist during the lifetime of the request.

Call addProduct method with product details. This returns a product ID if the action completed successfully. Otherwise it returns an error.

Call getProduct with the product ID. This returns product details if the action completed successfully. Otherwise it returns an error.
Close the connection when everything is done.

We have completed building the gRPC client in the Go language

gRPC: Building and Running

Building a Go Server

When we implement a Go service, the final package structure in the workspace looks like the following:
└─ productinfo
└─ service
├─ go.mod
├─ main.go
├─ productinfo_service.go
└─ ecommerce
└── product_info.pb.go
We can build our service to generate a service binary (bin/server). In order to build, first go to the Go module root directory location (productinfo/service) and execute the following shell command:
$ go build -i -v -o bin/server”

Building a Go Client

When we implement a Go client, the package structure in the workspace looks like:
└─ productinfo
└─ client
├─ go.mod
├──main.go
└─ ecommerce
└── product_info.pb.go
We can build the client code the same way we built the Go service using the following shell command:
$ go build -i -v -o bin/client

Running a Go Server and Client

We’ve just built a client and a server. Let’s run them on separate terminals and make them talk to each other:
// Running Server
$ bin/server
Starting gRPC listener on port :50051

// Running Client
$ bin/client

# REF
https://developers.google.com/protocol-buffers/docs/proto3#any
https://grpc.io
https://developers.google.com/protocol-buffers
Code and demo
https://grpc-up-and-running.github.io

About the Author

Trần Huy

View all author's posts

Bài viết khác

Use AWS to deploy your applications and services

Amazon Web Services (AWS) is a cloud computing platform that provides a wide range of services to help businesses and individuals build and deploy applications in the cloud. AWS offers a variety of services such as compute, storage, databases, networking, security, and more. In this guide, we will walk through the steps to get started […]

Use docker to run go project

Docker is a powerful tool that enables developers to create, deploy and run applications in a containerized environment. Using Docker to run Go projects has many advantages, including the ability to isolate your application from the underlying operating system, simplifying the deployment process, and allowing for greater scalability and flexibility. In this guide, we will […]

Install WSL for windows 10

1/ Enable feature Windows Subsystem for Linux Head to Control Panel > Programs > Turn Windows Features On Or Off. Enable the “Windows Subsystem for Linux” option in the list, and then click the “OK” button. Restart computer Now you can type on console: wsl –help 2/ Download ubuntu 18 from Microsoft Store or open […]

Make a binary file or script file run on startup on ubuntu

To make a binary file run on boot, you can create a startup script that will run the binary file when the operating system starts. The exact process for creating a startup script depends on the operating system you’re using. Here’s an example for a Linux operating system using the systemd init system: Create a […]

Explicit ssl bumping with Squid

To perform explicit SSL bumping with Squid, you need to perform the following steps: Generate a SSL certificate and key: You can either generate a self-signed certificate or obtain one from a certificate authority. The certificate and key will be used by Squid to encrypt and decrypt the traffic. Install and configure Squid: Squid is […]

Explicit ssl bumping with HAProxy

Basic guide About Explicit SSL bumping Explicit SSL bumping also known as “SSL interception,” is a feature of some reverse proxies and security appliances that allows the proxy to decrypt, inspect, and re-encrypt SSL/TLS encrypted traffic. The proxy acts as a man-in-the-middle, decrypting incoming SSL/TLS traffic and re-encrypting it before forwarding it to the destination […]