In this article I’m going to describe how to build simplest gRPC API. Building such API is consists of several steps:
- First of all, we have to define our API interface with protocol buffers or protobuf.
- Then we have to generate Golang server and client stubs with
protoccommand. - And finally we need to implement our API.
What our service will be doing is a calculations. At the moment it has just one
method Add which takes A and B and returns C as an addition result.
Project can be found on github.
Protocol buffers or protobuf
Here is calc.proto file with protobuf definition of the service:
syntax = "proto3";
option go_package = "pb";
message Request {
int32 a = 1;
int32 b = 2;
}
message Response {
int64 c = 1;
}
service Calc {
rpc Add (Request) returns (Response);
}
This definition contains Calc service with one Add method which takes message
Request with a and b fields and returns Response with c field. Numbers
in message definitions are called tags and used by gRPC server and client in
encoding/decoding messages procedures. In fact, it’s a field position inside
decoded message.
This definition is language independent, that means using this file we can
generate server and client stubs for any language supported by protobuf compiler
protoc.
protoc supports number of languages out-of-the-box, but for Golang we have to
use plugin. In our case we need plugin for Golang called protoc-gen-go.
Plugins are just a binaries which should be located in any directory available
in $PATH. Plugin name has a format protoc-gen-<suffix>, where:
protoc-gen-is a prefix.<suffix>- plugin short name without prefix.
Server and client stubs
Ok, let’s generate it:
protoc --go_out=plugins=grpc:. ./calc.proto
Here:
protocis a protobuf compiler command.go_outis a parameter which says to usegoplugin with nameprotoc-gen-go. In general, this parameter has a format<suffix>_out.plugins=grpcis a parameter for the plugin. It says to the plugin add an interface definition for our service into auto-generated file.:.part after semicolon is a output path.(current directory) for auto-generated files related tocalc.protofile../calc.protois a path to the service protobuf definition.
The command creates calc.pb.go file in the same directory with server and
client interfaces, structures for request and response and some other functions
for encoding/decoding messages.
Most interesting parts for us from the new file are Request and Response
structures and CalcServer/CalcClient interfaces, you can see below:
type Request struct {
A int32 `protobuf:"varint,1,opt,name=a,proto3" json:"a,omitempty"`
B int32 `protobuf:"varint,2,opt,name=b,proto3" json:"b,omitempty"`
}
type Response struct {
C int64 `protobuf:"varint,3,opt,name=c,proto3" json:"c,omitempty"`
}
type CalcServer interface {
Add(context.Context, *Request) (*Response, error)
}
type CalcClient interface {
Add(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
}
gRPC server implementation
We have protobuf service definition and auto-generated code by this definition. Now, we will be implementing gRPC server to be able to accept new connections and return responses.
type server struct{}
func (s *server) Add(ctx context.Context, req *pb.Request) (*pb.Response, error) {
fmt.Printf("got request: A = %d, B = %d\n", req.A, req.B)
return &pb.Response{
C: req.A + req.B,
}, nil
}
func main() {
s := grpc.NewServer()
pb.RegisterCalcServer(s, &server{})
lis, err := net.Listen("tcp", ":5001")
if err != nil {
return
}
s.Serve(lis)
}
server structure implements CalcServer interface from calc.pb.go file,
which represents gRPC server methods. In main function we:
- initialize empty gRPC server by calling
grpc.NewServer, it has no methods and it doesn’t listen for any connections yet. - register empty gRPC server with our implementation, i.e. we attach methods to the server.
- create a TCP listener for accepting incoming connections.
- start serve connections with created listener.
Build and start the server:
% go build && ./server
Now we need a client and here we are:
func main() {
cc, err := grpc.Dial("127.0.0.1:5001", grpc.WithInsecure())
if err != nil {
log.Fatal(err)
}
defer cc.Close()
client := pb.NewCalcClient(cc)
resp, err := client.Add(context.Background(), &pb.Request{A: 2, B: 2})
if err != nil {
log.Fatal(err)
}
fmt.Printf("response is: %d\n", resp.C)
}
In the client code we:
- initialize gRPC client connection
ccby callinggrpc.Dial. grpc.WithInsecurehere says grpc client to disable transport security.- initialize our
CalcClient. - call
Addmethod passing there request with some values.
Now, let’s build and run the client in the separate terminal session:
% go build && ./client
response is: 4
while in server terminal we’ll see request info like this:
% ./server
got request: A = 2, B = 2
That’s pretty enough to the simplest gRPC API.
