이야기박스

Go. gRPC 사용해보기 본문

Programming Language

Go. gRPC 사용해보기

박스님 2022. 5. 11. 16:00
반응형

gRPC는 java로만 개발을 해봤었는데, 이번에 go로도 사용할 기회가 생겨 테스트 코드 진행 겸 포스트를 남겨봅니다.

이번 포스팅은 아래의 gRPC 공식 문서의 Quick start를 바탕으로 작성되었습니다.

 

Quick start

This guide gets you started with gRPC in Go with a simple working example.

grpc.io

 

사전 준비

우선 protocol buffer 파일을 이용하여 코드를 생성하기 위해 아래 라이브러리를 설치합니다.

go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2

 

gRPC 코드 생성

Protocol buffers example

protobuf 파일은 공식 문서의 Quick start 예제를 그대로 참고하였습니다.

syntax = "proto3";

package hello;
option go_package = "story/grpc-protos/hello";

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  // Sends another greeting
  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

 

 

Generate go code

protoc --go_out=. --go_opt=paths=source_relative \
    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
    helloworld/helloworld.proto

 

코드 구성

이제 테스트를 위하여 gRPC Server / Client 각각의 테스트 코드를 생성해볼 예정입니다. 그전에 아래 go.mod의 grpc 및 protobuf 라이브러리를 추가해줍니다.

 

go.mod

google.golang.org/grpc v1.46.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect

 

gRPC server example

package main

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

	pb "story/grpc-protos/hello"
	"google.golang.org/grpc"
)

var (
	port = flag.Int("port", 50051, "The server port")
)

// server is used to implement helloworld.GreeterServer.
type server struct {
	pb.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	log.Printf("Received: %v", in.GetName())
	return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
	flag.Parse()
	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server{})
	log.Printf("server listening at %v", lis.Addr())
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

 

gRPC client example

package main

import (
	"context"
	"flag"
	"log"
	"time"

	pb "story/grpc-protos/hello"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

const (
	defaultName = "world"
)

var (
	addr = flag.String("addr", "localhost:50051", "the address to connect to")
	name = flag.String("name", defaultName, "Name to greet")
)

func main() {
	flag.Parse()
	// Set up a connection to the server.
	conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := pb.NewGreeterClient(conn)

	// Contact the server and print out its response.
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})
	if err != nil {
		log.Fatalf("could not greet: %v", err)
	}
	log.Printf("Greeting: %s", r.GetMessage())
}

 

테스트

gRPC Server

# server
$ go run ./hello/examples/server/hello_server.go
2022/05/11 16:59:47 server listening at [::]:50051
2022/05/11 16:59:58 Received: world

 

gRPC Client

# client
$ go run ./hello/examples/client/hello_client.go
2022/05/11 16:59:58 Greeting: Hello world

 

기타 내용

Protocol buffer 파일의 import 구성

프로토콜 버퍼 파일에도 import 구성을 통해 중복되는 내용을 밖으로 빼줄 수 있습니다.

 

## Before

syntax = "proto3";

package hello;
option go_package = "story/grpc-protos/box";

service Greeter {
  rpc SayStory (Box) returns (Box) {}
}

message Story {
  string name = 1;
}

message Box {
  Story wrapping = 1;
}

 

## After

syntax = "proto3";

package story.common;
option go_package = "story/grpc-protos/box";

import "story/common/story.proto";

service Greeter {
  rpc SayStory (Box) returns (Box) {}
}

message Box {
  Story wrapping = 1;
}
syntax = "proto3";

package story.common;
option go_package = "story/grpc-protos/box";

message Story {
  string name = 1;
}

위 처럼 두 개의 파일로 분리하여 관리가 가능합니다.

 

## 코드 생성

이렇게 분리된 코드들은 코드 생성 시 import 한 파일을 지정해주어야 하는데, 아래와 같은 옵션을 제공해주어야 합니다.

-I {package}={real path}

저는 생성시 아래와 같이 진행하였습니다.

protoc -I story/common=. \
    --go_out=. --go_opt=paths=import \
    --go-grpc_out=. --go-grpc_opt=paths=import \
    story/box.proto

 

후기

예전에 gRPC를 열심히 사용했었는데, 오랜만에 다시 쓰려고 하니까 처음 보는 것처럼 낯설더라고요. 이번에 한번 더 했으니, 한 동안 또 기억되길 기도해봅니다. ㅎㅎ

반응형