LoginSignup
1
0

More than 3 years have passed since last update.

【初めてのgRPC】Unary API開発ハンズオン

Posted at

gRPCとは

gRPCでは、クライアントアプリケーションは別のマシン上のサーバーアプリケーションのメソッドをローカルオブジェクトのように直接呼び出すことができるため、分散型のアプリケーションやサービスを簡単に作成できます。

また、別々の言語を持ったシステム同士をつなぐことも容易です。

image.png
参考:https://grpc.io/

今回はgRPCに関する詳しい生命は省くので、詳しくは下記のような記事を参照してください。
https://qiita.com/oohira/items/63b5ccb2bf1a913659d6

下記の記事で紹介されているように、REST APIより高いパフォーマンスも誇るようです。
https://qiita.com/muroon/items/1c9ad59653c00d8d5e3d

今回はとりあえずgRPCを動かしてみたいという方向けに、二つの数字をリクエストすると、その和がレスポンスとして返ってくるというシステムを構築してみます。

今回構築するのはgRPCの中でも、HTTP/2を使用し、1リクエスト-1レスポンスの方式をとるUnaryAPIを構築していきます。

環境構築

#grpcのインストール
$ go get -u google.golang.org/grpc

#Protocol Bufferのインストール
$ go get -u github.com/golang/protobuf/protoc-gen-go

protoファイルの作成

syntax = "proto3";

package calculator;
option go_package = "calculatorproto";

//リクエストとして投げるものを定義
message SumReq {
    int32 firstNum = 1;
    int32 secondNum = 2;
}


//レスポンスとして受け取るものを定義
message SumRes {
    int32 result = 1;
}

//上で定義したリクエストを引数に持ち、レスポンスを返すSumファンクションを書き出す。
service calculatorService{
    rpc Sum(SumReq) returns (SumRes){};
}

protoファイルから雛形コードの生成

次に上記のprotoファイルからコードを生成していきます。
以下のコマンドを叩いてください。

protoc calculator/proto/calculator.proto --go_out=plugins=grpc:.

すると何と先ほどのprotoファイルと同階層に下記のようなコードが生成されているはずです。

// Code generated by protoc-gen-go. DO NOT EDIT.
// source: calculator/proto/calculator.proto

package calculatorproto

import (
    context "context"
    fmt "fmt"
    proto "github.com/golang/protobuf/proto"
    grpc "google.golang.org/grpc"
    codes "google.golang.org/grpc/codes"
    status "google.golang.org/grpc/status"
    math "math"
)

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package

type SumReq struct {
    FirstNum             int32    `protobuf:"varint,1,opt,name=first_num,json=firstNum,proto3" json:"first_num,omitempty"`
    SecondNum            int32    `protobuf:"varint,2,opt,name=second_num,json=secondNum,proto3" json:"second_num,omitempty"`
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized     []byte   `json:"-"`
    XXX_sizecache        int32    `json:"-"`
}

func (m *SumReq) Reset()         { *m = SumReq{} }
func (m *SumReq) String() string { return proto.CompactTextString(m) }
func (*SumReq) ProtoMessage()    {}
func (*SumReq) Descriptor() ([]byte, []int) {
    return fileDescriptor_3d2a7eb5a895a616, []int{0}
}

func (m *SumReq) XXX_Unmarshal(b []byte) error {
    return xxx_messageInfo_SumReq.Unmarshal(m, b)
}
func (m *SumReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
    return xxx_messageInfo_SumReq.Marshal(b, m, deterministic)
}
func (m *SumReq) XXX_Merge(src proto.Message) {
    xxx_messageInfo_SumReq.Merge(m, src)
}
func (m *SumReq) XXX_Size() int {
    return xxx_messageInfo_SumReq.Size(m)
}
func (m *SumReq) XXX_DiscardUnknown() {
    xxx_messageInfo_SumReq.DiscardUnknown(m)
}

var xxx_messageInfo_SumReq proto.InternalMessageInfo

func (m *SumReq) GetFirstNum() int32 {
    if m != nil {
        return m.FirstNum
    }
    return 0
}

func (m *SumReq) GetSecondNum() int32 {
    if m != nil {
        return m.SecondNum
    }
    return 0
}

type SumRes struct {
    Result               int32    `protobuf:"varint,1,opt,name=result,proto3" json:"result,omitempty"`
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized     []byte   `json:"-"`
    XXX_sizecache        int32    `json:"-"`
}

func (m *SumRes) Reset()         { *m = SumRes{} }
func (m *SumRes) String() string { return proto.CompactTextString(m) }
func (*SumRes) ProtoMessage()    {}
func (*SumRes) Descriptor() ([]byte, []int) {
    return fileDescriptor_3d2a7eb5a895a616, []int{1}
}

func (m *SumRes) XXX_Unmarshal(b []byte) error {
    return xxx_messageInfo_SumRes.Unmarshal(m, b)
}
func (m *SumRes) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
    return xxx_messageInfo_SumRes.Marshal(b, m, deterministic)
}
func (m *SumRes) XXX_Merge(src proto.Message) {
    xxx_messageInfo_SumRes.Merge(m, src)
}
func (m *SumRes) XXX_Size() int {
    return xxx_messageInfo_SumRes.Size(m)
}
func (m *SumRes) XXX_DiscardUnknown() {
    xxx_messageInfo_SumRes.DiscardUnknown(m)
}

var xxx_messageInfo_SumRes proto.InternalMessageInfo

func (m *SumRes) GetResult() int32 {
    if m != nil {
        return m.Result
    }
    return 0
}

func init() {
    proto.RegisterType((*SumReq)(nil), "calculator.SumReq")
    proto.RegisterType((*SumRes)(nil), "calculator.SumRes")
}

func init() { proto.RegisterFile("calculator/proto/calculator.proto", fileDescriptor_3d2a7eb5a895a616) }

var fileDescriptor_3d2a7eb5a895a616 = []byte{
    // 174 bytes of a gzipped FileDescriptorProto
    0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4c, 0x4e, 0xcc, 0x49,
    0x2e, 0xcd, 0x49, 0x2c, 0xc9, 0x2f, 0xd2, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0xd7, 0x47, 0x08, 0xe8,
    0x81, 0x05, 0x84, 0xb8, 0x10, 0x22, 0x4a, 0x2e, 0x5c, 0x6c, 0xc1, 0xa5, 0xb9, 0x41, 0xa9, 0x85,
    0x42, 0xd2, 0x5c, 0x9c, 0x69, 0x99, 0x45, 0xc5, 0x25, 0xf1, 0x79, 0xa5, 0xb9, 0x12, 0x8c, 0x0a,
    0x8c, 0x1a, 0xac, 0x41, 0x1c, 0x60, 0x01, 0xbf, 0xd2, 0x5c, 0x21, 0x59, 0x2e, 0xae, 0xe2, 0xd4,
    0xe4, 0xfc, 0xbc, 0x14, 0xb0, 0x2c, 0x13, 0x58, 0x96, 0x13, 0x22, 0xe2, 0x57, 0x9a, 0xab, 0xa4,
    0x00, 0x35, 0xa5, 0x58, 0x48, 0x8c, 0x8b, 0xad, 0x28, 0xb5, 0xb8, 0x34, 0xa7, 0x04, 0x6a, 0x04,
    0x94, 0x67, 0xe4, 0xc2, 0x25, 0x88, 0xb0, 0x35, 0x38, 0xb5, 0xa8, 0x2c, 0x33, 0x39, 0x55, 0x48,
    0x9f, 0x8b, 0x39, 0xb8, 0x34, 0x57, 0x48, 0x48, 0x0f, 0xc9, 0x89, 0x10, 0xd7, 0x48, 0x61, 0x8a,
    0x15, 0x2b, 0x31, 0x38, 0x09, 0x46, 0xf1, 0x23, 0x84, 0xc1, 0x9e, 0x49, 0x62, 0x03, 0x53, 0xc6,
    0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xbe, 0xa4, 0xa3, 0x3b, 0xf8, 0x00, 0x00, 0x00,
}

// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn

// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4

// CalculatorServiceClient is the client API for CalculatorService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type CalculatorServiceClient interface {
    Sum(ctx context.Context, in *SumReq, opts ...grpc.CallOption) (*SumRes, error)
}

type calculatorServiceClient struct {
    cc *grpc.ClientConn
}

func NewCalculatorServiceClient(cc *grpc.ClientConn) CalculatorServiceClient {
    return &calculatorServiceClient{cc}
}

func (c *calculatorServiceClient) Sum(ctx context.Context, in *SumReq, opts ...grpc.CallOption) (*SumRes, error) {
    out := new(SumRes)
    err := c.cc.Invoke(ctx, "/calculator.calculatorService/Sum", in, out, opts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}

// CalculatorServiceServer is the server API for CalculatorService service.
type CalculatorServiceServer interface {
    Sum(context.Context, *SumReq) (*SumRes, error)
}

// UnimplementedCalculatorServiceServer can be embedded to have forward compatible implementations.
type UnimplementedCalculatorServiceServer struct {
}

func (*UnimplementedCalculatorServiceServer) Sum(ctx context.Context, req *SumReq) (*SumRes, error) {
    return nil, status.Errorf(codes.Unimplemented, "method Sum not implemented")
}

func RegisterCalculatorServiceServer(s *grpc.Server, srv CalculatorServiceServer) {
    s.RegisterService(&_CalculatorService_serviceDesc, srv)
}

func _CalculatorService_Sum_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
    in := new(SumReq)
    if err := dec(in); err != nil {
        return nil, err
    }
    if interceptor == nil {
        return srv.(CalculatorServiceServer).Sum(ctx, in)
    }
    info := &grpc.UnaryServerInfo{
        Server:     srv,
        FullMethod: "/calculator.calculatorService/Sum",
    }
    handler := func(ctx context.Context, req interface{}) (interface{}, error) {
        return srv.(CalculatorServiceServer).Sum(ctx, req.(*SumReq))
    }
    return interceptor(ctx, in, info, handler)
}

var _CalculatorService_serviceDesc = grpc.ServiceDesc{
    ServiceName: "calculator.calculatorService",
    HandlerType: (*CalculatorServiceServer)(nil),
    Methods: []grpc.MethodDesc{
        {
            MethodName: "Sum",
            Handler:    _CalculatorService_Sum_Handler,
        },
    },
    Streams:  []grpc.StreamDesc{},
    Metadata: "calculator/proto/calculator.proto",
}


このコードには特に手を加える必要がありません。このコードで定義されたものに乗っ取って、server側client側のコードを書いていくだけです。

server側

server.go
package main

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

    calculatorproto "github.com/waytkheming/grpc-go-course/calculator/proto"
    "google.golang.org/grpc"
)

type server struct{}

//Sumファンクションの中身を記載
func (*server) Sum(ctx context.Context, req *calculatorproto.SumReq) (*calculatorproto.SumRes, error) {
    fmt.Printf("calculator function %v", req)
    firstNum := req.FirstNum
    secondNum := req.SecondNum
    sum := firstNum + secondNum
    res := &calculatorproto.SumRes{
        Result: sum,
    }

    return res, nil
}

func main() {
    lis, err := net.Listen("tcp", "0.0.0.0:50051")
    if err != nil {
        log.Fatalf("Failed to listen: %v", err)
    }

    s := grpc.NewServer()
    calculatorproto.RegisterCalculatorServiceServer(s, &server{})

    if err := s.Serve(lis); err != nil {
        log.Fatalf("Failed to serve: %v", err)
    }
}


client側

package main

import (
    "fmt"
    "log"

    "context"

    calculatorproto "github.com/waytkheming/grpc-go-course/calculator/proto"
    "google.golang.org/grpc"
)

func main() {
    fmt.Println("Hello from client")
    conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
    if err != nil {
        log.Fatalf("could not connect:%v", err)
    }

    defer conn.Close()

    c := calculatorproto.NewCalculatorServiceClient(conn)

    doUnary(c)
}
//リクエストとして投げるものを定義
func doUnary(c calculatorproto.CalculatorServiceClient) {

    req := &calculatorproto.SumReq{
        FirstNum:  11,
        SecondNum: 22,
    }

    res, err := c.Sum(context.Background(), req)
    if err != nil {
        log.Fatalf("error while calling GRPC: %v", err)
    }
    log.Printf("Response from Sum: %v", res.Result)
}



ここで書かれているUnaryとはRPC方式の中でもシンプルな1 Request - 1 Responseの形式を持つものの名称です。

APIの実行

ここまで実装したら、server側client側双方でgo runをして実行してみてください。
すると、下記のようなレスポンスが返ってくると思います。

2019/08/17 22:34:22 Response from Greet: 33

以上になります。ここに書かれていることを応用すれば、簡単な1req-1res方式のAPIは構築できるはずです!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0