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. And 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;
}
There's the explanation for the syntax:
- syntax = "proto3";
: This is mean that the protobuf file will use syntax
version 3.
- package main;
: This is mean that it will be placed as a main package in
the directory.
- option go_package = "./dataserver";
: This is mean 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
for 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 be existed
after we install 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 to 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.
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
Comments
Post a Comment