In this example we are going to create a simple gRPC client and server application. Simply the client is sending a request and receiving a response from server. I am not going to get into details of what gRPC and proto files but added some links to the bottom.


There are two important things. The first one is the client and server applications share the same protobuf file. The second one is, if you update the proto file, you must recompile it to reflect the changes to its *.pb.go file.


Prerequisites


Init go modules


go mod init github.com/YOU/bank

Install gRPC


go get -u google.golang.org/grpc

Install protobuf


# MacOS
brew install protobuf

Install protoc-gen-go


go get -u github.com/golang/protobuf/protoc-gen-go

Structure


When you build a gRPC application, you first create a *.proto file and compile it then start developing your application.


├── Makefile
├── client
│   └── main.go
├── go.mod
├── go.sum
├── pkg
│   └── proto
│   └── credit
│   ├── credit.pb.go
│   └── credit.proto
└── server
└── main.go

Files


Makefile


.PHONY: compile
compile: ## Compile the proto file.
protoc -I pkg/proto/credit/ pkg/proto/credit/credit.proto --go_out=plugins=grpc:pkg/proto/credit/

.PHONY: server
server: ## Build and run server.
go build -race -ldflags "-s -w" -o bin/server server/main.go
bin/server

.PHONY: client
client: ## Build and run client.
go build -race -ldflags "-s -w" -o bin/client client/main.go
bin/client

credit.proto


syntax = "proto3";

package credit;

message CreditRequest {
float amount = 1;
}

message CreditResponse {
string confirmation = 1;
}

service CreditService {
rpc Credit(CreditRequest) returns (CreditResponse) {}
}

credit.pb.go


I am not adding the content here because it is generated with the command below.


make compile

client/main.go


package main

import (
"context"
"log"
"time"

"github.com/YOU/bank/pkg/proto/credit"
"google.golang.org/grpc"
)

func main() {
log.Println("Client running ...")

conn, err := grpc.Dial(":50051", grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalln(err)
}
defer conn.Close()

client := credit.NewCreditServiceClient(conn)

request := &credit.CreditRequest{Amount: 1990.01}

ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

response, err := client.Credit(ctx, request)
if err != nil {
log.Fatalln(err)
}

log.Println("Response:", response.GetConfirmation())
}

server/main.go


package main

import (
"context"
"fmt"
"log"
"net"

"github.com/YOU/bank/pkg/proto/credit"
"google.golang.org/grpc"
)

type server struct {
credit.UnimplementedCreditServiceServer
}

func main() {
log.Println("Server running ...")

lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalln(err)
}

srv := grpc.NewServer()
credit.RegisterCreditServiceServer(srv, &server{})

log.Fatalln(srv.Serve(lis))
}

func (s *server) Credit(ctx context.Context, request *credit.CreditRequest) (*credit.CreditResponse, error) {
log.Println(fmt.Sprintf("Request: %g", request.GetAmount()))

return &credit.CreditResponse{Confirmation: fmt.Sprintf("Credited %g", request.GetAmount())}, nil
}

Test


$ make server
go build -race -ldflags "-s -w" -o bin/server server/main.go
bin/server
2020/04/04 18:07:37 Server running ...

$ make client
go build -race -ldflags "-s -w" -o bin/client client/main.go
bin/client
2020/04/04 18:07:42 Client running ...
2020/04/04 18:07:42 Response: Credited 1990.01

When you run client code above, the server will also output message below.


2020/04/04 18:07:42 Request: 1990.01

References