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.–
structure. It will have
string key = 1;
: This is the field that will exist in the datastructure. 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
Leave a Reply