21/07/2020 - GO
When we create a gRPC client and server application, they normally share same protobuf files to handle communication between each other. Instead of duplicating protobuf files within both applications, we could keep them in a library then let both applications import it which is what we are going to do here.
├── Makefile
├── bank
│ └── account
│ ├── balance.pb.go
│ └── balance.proto
└── go.mod
module github.com/inanzzz/goproto
go 1.13
require (
github.com/golang/protobuf v1.4.2
golang.org/x/net v0.0.0-20200707034311-abcdefg // indirect
golang.org/x/sys v0.0.0-20200720211630-abcdefg // indirect
golang.org/x/text v0.3.3 // indirect
google.golang.org/genproto v0.0.0-20200721032028-abcdefg // indirect
google.golang.org/grpc v1.30.0
google.golang.org/protobuf v1.25.0
)
.PHONY: compile
compile:
protoc --go_out=plugins=grpc:. --go_opt=paths=source_relative bank/account/*.proto
syntax = "proto3";
package account;
option go_package = "github.com/inanzzz/goproto/bank/account";
// Deposit --------------
message DepositRequest {
float amount = 1;
}
message DepositResponse {
bool ok = 1;
}
// Withdraw -------------
message WithdrawRequest {
float amount = 1;
}
message WithdrawResponse {
bool ok = 1;
}
// Service --------------
service BalanceService {
// Money in
rpc Deposit(DepositRequest) returns (DepositResponse) {}
// Money out
rpc Withdraw(WithdrawRequest) returns (WithdrawResponse) {}
}
// Transaction:
// Credit = positive (+), a credit is money coming in of the account
// Debit = negative (-), a debit is money going out of the account
Run make compile
command to generate this file.
As per your needs, use any command below in your client and server applications where this library is needed.
go get -u github.com/inanzzz/goproto
go get -u github.com/inanzzz/goproto@{branch_name}
go get -u github.com/inanzzz/goproto@{commit_hash}
├── cmd
│ └── server
│ └── main.go
├── go.mod
└── internal
└── bank
└── account
└── balance.go
package main
import (
"log"
"net"
"github.com/inanzzz/client/internal/bank/account"
"google.golang.org/grpc"
pb "github.com/inanzzz/goproto/bank/account"
)
func main() {
log.Println("Server running ...")
listener, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalln(err)
}
server := grpc.NewServer()
pb.RegisterBalanceServiceServer(server, account.NewBalanceServer())
log.Fatalln(server.Serve(listener))
}
package account
import (
"context"
"log"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
pb "github.com/inanzzz/goproto/bank/account"
)
type BalanceServer struct {
pb.UnimplementedBalanceServiceServer
}
func NewBalanceServer() BalanceServer {
return BalanceServer{}
}
func (BalanceServer) Deposit(ctx context.Context, req *pb.DepositRequest) (*pb.DepositResponse, error) {
log.Println(req.GetAmount())
if req.GetAmount() < 0 {
return nil, status.Errorf(codes.InvalidArgument, "cannot deposit %v", req.GetAmount())
}
return &pb.DepositResponse{Ok: true}, nil
}
func (BalanceServer) Withdraw(ctx context.Context, req *pb.WithdrawRequest) (*pb.WithdrawResponse, error) {
log.Println(req.GetAmount())
if req.GetAmount() < 0 {
return nil, status.Errorf(codes.InvalidArgument, "cannot withdraw %v", req.GetAmount())
}
return &pb.WithdrawResponse{Ok: true}, nil
}
├── cmd
│ └── server
│ └── main.go
├── go.mod
└── internal
└── bank
└── account
└── balance.go
package main
import (
"context"
"log"
"time"
"github.com/inanzzz/client/internal/bank/account"
"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()
var ok bool
balanceClient := account.NewBalanceClient(conn, time.Second)
ok, err = balanceClient.Deposit(context.Background(), 1990.01)
log.Println(ok)
log.Println(err)
ok, err = balanceClient.Withdraw(context.Background(), 1990.01)
log.Println(ok)
log.Println(err)
}
package account
import (
"context"
"fmt"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/status"
pb "github.com/inanzzz/goproto/bank/account"
)
type BalanceClient struct {
client pb.BalanceServiceClient
timeout time.Duration
}
func NewBalanceClient(conn *grpc.ClientConn, timeout time.Duration) BalanceClient {
return BalanceClient{
client: pb.NewBalanceServiceClient(conn),
timeout: timeout,
}
}
func (b BalanceClient) Deposit(ctx context.Context, amount float32) (bool, error) {
request := &pb.DepositRequest{Amount: amount}
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(b.timeout))
defer cancel()
response, err := b.client.Deposit(ctx, request)
if err != nil {
if er, ok := status.FromError(err); ok {
return false, fmt.Errorf("grpc: %s, %s", er.Code(), er.Message())
}
return false, fmt.Errorf("server: %s", err.Error())
}
return response.GetOk(), nil
}
func (b BalanceClient) Withdraw(ctx context.Context, amount float32) (bool, error) {
request := &pb.WithdrawRequest{Amount: amount}
ctx, cancel := context.WithTimeout(ctx, b.timeout)
defer cancel()
response, err := b.client.Withdraw(ctx, request)
if err != nil {
if er, ok := status.FromError(err); ok {
return false, fmt.Errorf("grpc: %s, %s", er.Code(), er.Message())
}
return false, fmt.Errorf("server: %s", err.Error())
}
return response.GetOk(), nil
}