Develop RPC Connection with Golang

    Remote Procedure Call (RPC) is a communication model that allows the client to call a procedure from another computer as if the procedure existed locally. For example, if you have a function called add(a, b) in another server and the function is already registered in the RPC program, then you can call that function without defining the function in your local computer. After the client calls the procedure in the server, the server will return the result of the procedure.
    Nowadays, many companies use this communication model for their internal services. This is because the user usually doesn’t need to know how the internal services communicate with each other. Because of its simplicity, RPC will increase the performance of the internal services. Aside from that, it can reduce the effort to redevelop code to call each other internal services.

    In this article, we’ll try to create a simple RPC program with Golang. We will use grpc it to help us with the RPC program. grpc is an RPC framework that is quite easy to use and already matured.
    For the data structure that will be exchanged between the client and server, we will use Protocol buffers or we can call it protobuf. grpc itself using this data structure. Protobuf is like JSON, but smaller and faster. To be able to use protobuf, we need to install a protocol buffer compiler (protoc) on our computer first. Because the installation is really dependent on your machine, you can try this link.
    Then let’s create three directories for our project, let’s name them client, server, and dataserver. The client will hold the client code, the server will hold the server code, and then the dataserver will hold the proto file for the data structure that will be used.

    After that. let’s create the data structure. Create a file named data.proto at the dataserver directory. Put this code in it.
syntax = "proto3";
package main;

option go_package = "./dataserver";

service DataServer {
	rpc GetData(Request) returns (Data) {}
}

message Data {
	string key = 1;
	int32 value = 2;
}

message Request {
	string key = 1;
}    
    Here’s the explanation for the syntax:
syntax = "proto3";: This means that the protobuf file will use syntax version 3.
package main;: This means that it will be placed as a main package in the directory.
option go_package = "./dataserver";: This means that the created data structure will be placed in dataserver package.
service DataServer{}: This is the base of the server interface and client struct for the protobuf.
rpc GetData(Request) returns (Data) {}: This is the shape of the function that will be placed in the server.
message Data{}: This is the shape of the data structure that will be exchanged between the client and the server.
string key = 1;: This is the field that will exist in the data
structure. It will have key as the name, with string as its data type. The number 1 is used to identify the fields in the message binary format, so just use some numbers in order starting from 1 the fields.
    Then we need to create the Golang codes for the data structure from the proto file. To achieve that, we can go to the dataserver directory and use this command:
  protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=.
  --go-grpc_opt=paths=source_relative data.proto
    Let me explain the command:
protoc: it’s the protocol buffer compiler command that should have existed after we installed it before.
--go_out=.: The output of the data structure that will go to the current directory which should be the dataserver directory.
--go_opt=paths=source_relative: go_opt will help us set up a variable for the compiler command. And we specify the path for the source of the data structure which is the current directory.
--go-grpc_out=.: This output of the interface for the gRPC server will go to the current directory, which should be the dataserver directory.
--go-grpc_opt=paths=source_relative: The same as the --go_opt, but it is used for the interface of the gRPC server.
data.proto: The proto file that we created before. We can specify more than one proto file.
    Then we should have new files like these:

After this, I advise you to commit the changes and push them to your online repository first, because we will use the files in the dataserver directory for the server and client.

    Then let’s create the server codes that will implement the gRPC server interface. Create a file named data.go first in the server directory. Then fill it with this code:

package main

import (
	pb "github.com/kuuhaku86/simple-go-rpc/dataserver"
)

var Datas []pb.Data = []pb.Data{
	pb.Data{
		Key:   "a",
		Value: 10,
	},
	pb.Data{
		Key:   "b",
		Value: 100,
	},
	pb.Data{
		Key:   "c",
		Value: 1000,
	},
}
    For the import pb part, you can use your own Github URL because I believe we have a different URL for the Github URL :). This code will be used as the data that will be sent to the client. The client will fetch the data with the same key as the requested request.
    Then we can create the struct that will implement the gRPC server interface. Create a file named dataServer.go. Then we can fill it with this code:
package main

import (
	"errors"
	"context"

	pb "github.com/kuuhaku86/simple-go-rpc/dataserver"
)

type DataServer struct {}

func (server *DataServer) GetData(ctx context.Context, request *pb.Request) (*pb.Data, error) {
	for _, data := range Datas {
		if request.Key == data.Key {
			return &data, nil
		}
	}

	return nil, errors.New("Data not found")
}

func newServer() *DataServer {
	s := &DataServer{}
	return s
}      
    Well, it’s just an ordinary Golang code. But we should implement the GetData function and add Context it as the parameter. Then we add the Request data structure as the parameter and the Data data structure for the return data type. And we add error as the return value too if an error occurs.
    The code will loop the Datas array and check if the data key is the same as the request. Then if we found one, we return the data to the client. If we don’t find one, we can just return nil.
    Then we make the code to run the server. Create a file named main.go and fill the file with this code:
package main

import (
	"log"
	"net"

	"google.golang.org/grpc"
	pb "github.com/kuuhaku86/simple-go-rpc/dataserver"
)

func main() {
	listener, err := net.Listen("tcp", "localhost:8600")

	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}

	var opts []grpc.ServerOption

	grpcServer := grpc.NewServer(opts...)
	pb.RegisterDataServerServer(grpcServer, newServer())
	grpcServer.Serve(listener)
}
    The code will run our server with 8600 as the port. Well, you can use any other port that you want.
    Don’t forget to initialize the go module at the server directory with go mod init package_name command.
    Then let’s go to the client. Create main.go to put our client code. Then fill the file with this code:
package main

import (
	"log"
	"context"

	"google.golang.org/grpc"
	pb "github.com/kuuhaku86/simple-go-rpc/dataserver"
)

func main() {
	conn, err := grpc.Dial("localhost:8600", grpc.WithInsecure())
	if err != nil {
		log.Fatalf("fail to dial: %v", err)
	}
	defer conn.Close()

	client := pb.NewDataServerClient(conn)

	data, err := client.GetData(context.Background(), &pb.Request{Key: "a"})
	if err != nil {
		log.Fatalf("client.GetData failed: %v", err)
	} else {
		log.Printf("Result: %d", data.Value)
 	}
 
 	data, err = client.GetData(context.Background(), &pb.Request{Key: "b"})
 	if err != nil {
 		log.Fatalf("client.GetData failed: %v", err)
 	} else {
 		log.Printf("Result: %d", data.Value)
 	}

 	data, err = client.GetData(context.Background(), &pb.Request{Key: "c"})
 	if err != nil {
 		log.Fatalf("client.GetData failed: %v", err)
 	} else {
 		log.Printf("Result: %d", data.Value)
 	}
}
  In this code, we just try to connect to the server without the TLS/SSL so we place grpc.WithInsecure() the second argument for the Dial function. Well if you want a secure connection, you can specify it in that place with Option the structure from the grpc package. Then we fetch the data one by one for each key.
    Don’t forget to init the go module for the client directory with the same command as before.
    And if we run the client and the server, we should have this output at our terminal.

Well, you can see the code directly from this repository.

   You can add more functionality for this code like maybe the database system, authentication system, and the other functionality you want. But I want to keep the article to the basic functionality so it just fetches some data from the server.
    Thank you very much, you can contact me if you want to discuss something.
Sources:
– https://grpc.io/docs/what-is-grpc/introduction/
– https://www.techtarget.com/searchapparchitecture/definition/Remote-Procedure-Call-RPC
– https://en.wikipedia.org/wiki/Remote_procedure_call
– https://developers.google.com/protocol-buffers/docs/overview