Bir gRPC istemci ve sunucu uygulaması oluşturduğumuzda, normalde birbirleriyle iletişimi sağlamak için aynı protobuf dosyalarını paylaşırlar. Protobuf dosyalarını her iki uygulamada da tutmak yerine, bunları bir kod kütüphanesinde tutabilir ve her iki uygulamanın da içe aktarmasına izin verebiliriz, ki burada yapacağımız şey budur.


Kütüphane


Yapı


├── Makefile
├── bank
│   └── account
│   ├── balance.pb.go
│   └── balance.proto
└── go.mod

Dosyalar


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
)

Makefile

.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

Bu dosyayı yaratmak için make compile komutunu çalıştırın.


Kullanım


İhtiyaçlarınıza göre, bu kitaplığın gerekli olduğu istemci ve sunucu uygulamalarınızda aşağıdaki komutları kullanabilirsiniz.


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}

Sunucu


Yapı


├── cmd
│   └── server
│   └── main.go
├── go.mod
└── internal
└── bank
└── account
└── balance.go

Dosyalar


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
}

İstemci


Yapı


├── cmd
│   └── server
│   └── main.go
├── go.mod
└── internal
└── bank
└── account
└── balance.go

Dosyalar


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
}