当我们创建一个gRPC客户端和服务器应用程序时,它们通常共享相同的protobuf文件来处理彼此间的通信。与其在两个应用程序中重复protobuf文件,我们可以把它们放在一个库中,然后让两个应用程序导入,这就是我们要做的。
库
结构
├── Makefile
├── bank
│ └── account
│ ├── balance.pb.go
│ └── balance.proto
└── go.mod
文件
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
bank/account/balance.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
bank/account/balance.pb.go
运行make compile 命令来生成这个文件。
使用方法
根据你的需要,在需要这个库的客户和服务器应用程序中使用以下任何命令。
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
文件
cmd/server/main.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))
}
internal/bank/account/balance.go
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
文件
cmd/client/main.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)
}
internal/bank/account/balance.go
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
}