创建一个gRPC protobuf库—在客户端和服务器端Golang应用程序中使用

106 阅读2分钟

当我们创建一个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
}