[gRPC] Making gRPC API with Protocol buffers


Post Series (click to expand)

Implementing gRPC server with Protobuf, Golang, Flutter

πŸ‘‰ Part 1 - [gRPC] Making gRPC API with Protocol buffers
πŸ‘‰ Part 2 - [gRPC] Simple gRPC calls with Golang and Flutter

Protocol Buffers μ΄μš©ν•˜μ—¬ gRPC API λ§Œλ“€κΈ°

Protocol Buffers λ₯Ό μ‚¬μš©ν•˜μ—¬ κ°„λ‹¨ν•œ gRPC λͺ…μ„Έλ₯Ό μž‘μ„±ν•˜κ³ , protobuf compiler λ₯Ό 톡해 μƒμ„±λœ μ½”λ“œλ₯Ό dart(flutter), golang μ—μ„œ μ΄μš©ν•˜λŠ” 방법을 μ„€λͺ…ν•œλ‹€.

Backgrounds

gRPC

gRPC is a modern open source high performance Remote Procedure Call (RPC) framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.

Google 이 κ°œλ°œν•œ Remote Procedure Call (RPC) framework. μ„œλ²„-ν΄λΌμ΄μ–ΈνŠΈ κ°„ μΈν„°νŽ˜μ΄μŠ€λ₯Ό ꡬ좕할 λ•Œ, HTTP REST + json 방식과 ν•¨κ»˜ κ°€μž₯ 많이 μ‚¬μš©λ˜λŠ” 방식이닀.

Protocol Buffers

Protocol buffers provide a language-neutral, platform-neutral, extensible mechanism for serializing structured data in a forward-compatible and backward-compatible way. It’s like JSON, except it’s smaller and faster, and it generates native language bindings. Β 
Β 
Protocol buffers are a combination of the definition language (created in .proto files), the code that the proto compiler generates to interface with data, language-specific runtime libraries, and the serialization format for data that is written to a file (or sent across a network connection).

μ—­μ‹œ Google 이 κ°œλ°œν•œ 톡신 κ·œκ²©μ΄λ‹€. 직렬화/μ—­μ§λ ¬ν™”λ‘œ μ‹€μ œ network layer μ—μ„œλŠ” byte format 으둜 ν†΅μ‹ ν•˜λ©°, μ‚¬μš©λ²• μžμ²΄λŠ” JSON κ³Ό 거의 μœ μ‚¬ν•˜μ§€λ§Œ 자체 μ–Έμ–΄λ‘œ message κ·œκ²©μ„ μ •μ˜ν•˜κ³  protocol buffer compiler 둜 μ‚¬μš©ν•˜λ €λŠ” language 에 λŒ€ν•œ binding code λ₯Ό 생성, μž„ν¬νŠΈν•˜μ—¬ μ‚¬μš©ν•œλ‹€. JSON 에 λΉ„ν•΄ λΉ λ₯΄κ³  κ°€λ²Όμ›Œμ„œ gRPC 와 ν•¨κ»˜ 주둜 microservice 의 κ΅¬ν˜„μ— 많이 μ‚¬μš©ν•˜λŠ” νŽΈμ΄λ‹€.

Exp.

λ‚΄ κ²½μš°μ—λŠ” νšŒμ‚¬μ—μ„œλŠ” HTTP + protobuf, HTTP + json 방식을 μ‚¬μš©ν•˜κ³  있고, 개인 ν”„λ‘œμ νŠΈμ—μ„œλŠ” gRPC + protobuf λ₯Ό μ‚¬μš©ν•œλ‹€. 각각 μž₯단점은 뚜렷이 λ‚˜νƒ€λ‚˜λŠ” 반면, μ’…ν•©μ μœΌλ‘œλŠ” μ–΄λ–€ 방식이 더 λ‚«λ‹€λ₯Ό νŒκ°€λ¦„ν•˜κΈ°λŠ” νž˜λ“  것 κ°™λ‹€.

API specification with Protocol Buffers

Defining APIs/messages with proto3 language

λ¨Όμ € proto3 language λ₯Ό μ΄μš©ν•˜μ—¬ 톡신에 μ‚¬μš©ν•  message 듀을 μ •μ˜ν•œλ‹€. proto3 language 의 ꡬ체적인 μ‚¬μš© 방법은 곡식 λ¬Έμ„œ λ₯Ό μ°Έμ‘°ν•˜μž.

syntax = "proto3";
package protocols;

option go_package = "protocols/goapi";

message User {
  string id = 1;
  string email = 2;
  string name = 3;
}

service UserApi {
  rpc GetUser(GetUserRequest) returns (GetUserReply) {}
  rpc AddUser(AddUserRequest) returns (AddUserReply) {}
}

message GetUserRequest {
  string email = 1;
}
message GetUserReply {
  repeated User users = 1;
}

message AddUserRequest {
  string email = 1;
  string name = 2;
}
message AddUserReply {
  User user = 1;
}
api_user.proto

같은 message format 을 μ—¬λŸ¬ κ΅°λ°μ„œ μ‚¬μš©ν•  경우 ν•˜λ‚˜μ˜ common message format 을 μž¬μ‚¬μš©ν•˜μ§€ μ•Šκ³ , 각각의 μš©λ„μ— 따라 message name 을 λ‹€λ₯΄κ²Œ ν•˜μ—¬ μ „λΆ€ μ •μ˜ν•΄ μ£ΌλŠ” 것이 μ’‹λ‹€. κ·Έ 편이 λ‚˜μ€‘μ— API μš”κ΅¬μ‚¬ν•­μ˜ 변화에 따라 message ꡬ쑰λ₯Ό μˆ˜μ •ν•˜λ”λΌλ„ λ‹€λ₯Έ API 에 영ν–₯을 λ―ΈμΉ˜μ§€ μ•Šκ³  ν•„μš”ν•œ λΆ€λΆ„λ§Œ μˆ˜μ •ν•  수 있기 λ•Œλ¬Έμ΄λ‹€.

Compile to generate language-bindings

이제 μ„œλ²„μ™€ ν΄λΌμ΄μ–ΈνŠΈλ₯Ό κ΅¬ν˜„ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λ  binding code λ₯Ό μƒμ„±ν•œλ‹€.

Installing protoc compiler and plugins

.proto νŒŒμΌμ„ μ»΄νŒŒμΌν•˜λ €λ©΄ protoc protobuf compiler λ₯Ό μ„€μΉ˜ν•΄μ•Ό ν•œλ‹€. λ˜ν•œ μ‚¬μš©ν•˜λ €λŠ” ν”„λ‘œκ·Έλž˜λ° 언어별 plugin 도 μ„€μΉ˜λ˜μ–΄ μžˆμ–΄μ•Ό ν•œλ‹€.

Compiling

ν”„λ‘œμ νŠΈλ₯Ό μ§„ν–‰ν•˜λ©΄μ„œ, API 의 μˆ˜μ • 및 μΆ”κ°€ λ“±μœΌλ‘œ 인해 반볡적으둜 μ‚¬μš©λ  κ²ƒμ΄λ―€λ‘œ shell script λ₯Ό μž‘μ„±ν•˜λŠ” 것을 μΆ”μ²œν•œλ‹€.

#!/bin/bash

SRC_DIR=$(pwd)
DART_OUT=$SRC_DIR/dartapi/lib
GO_DIR=$SRC_DIR/goapi

rm -rf "$GO_DIR"
rm -rf "$DART_OUT"
mkdir -p "$GO_DIR"
mkdir -p "$DART_OUT"

protoc \
-I="$SRC_DIR" \
--dart_out=grpc:"$DART_OUT" \
--go_out="$SRC_DIR" \
--go_opt=module=protocols \
--go-grpc_out="$SRC_DIR" \
--go-grpc_opt=module=protocols \
"$SRC_DIR"/*.proto
gen.sh

golang κ΄€λ ¨ μ˜΅μ…˜μ΄ μ’€ λ§Žλ‹€. golang binding code 의 output μœ„μΉ˜λ₯Ό ν˜„μž¬ 디렉토리 SRC_DIR 둜 지정해 μ£Όκ³ , option 으둜 module prefix (μ„€λͺ… μ°Έκ³ )λ₯Ό μ„€μ •ν•΄μ€€λ‹€. μ΄λ ‡κ²Œ μ„€μ •ν•˜λ©΄ μ•„λž˜μ™€ 같은 ꡬ쑰둜 output 이 μƒμ„±λœλ‹€.

β”œβ”€β”€ dartapi
β”‚Β Β  └── lib
β”‚Β Β      β”œβ”€β”€ api_user.pb.dart
β”‚Β Β      β”œβ”€β”€ api_user.pbenum.dart
β”‚Β Β      β”œβ”€β”€ api_user.pbgrpc.dart
β”‚Β Β      └── api_user.pbjson.dart
└── goapi
β”‚   β”œβ”€β”€ api_user.pb.go
β”‚   └── api_user_grpc.pb.go
β”œβ”€β”€ api_user.proto
└── gen.sh

server side μ—μ„œ go modules λ₯Ό 톡해 μž„ν¬νŠΈν•  수 μžˆλ„λ‘ go.mod νŒŒμΌμ„ μΆ”κ°€ν•œλ‹€. ν”„λ‘œμ νŠΈ 루트 λ””λ ‰ν† λ¦¬μ—μ„œ,

go mod init protocols && go mod tidy

ν•˜λ©΄ μ•„λž˜μ™€ 같이 go.mod 파일이 μƒμ„±λœλ‹€.

module protocols

go 1.17

require (
	google.golang.org/grpc v1.45.0
	google.golang.org/protobuf v1.28.0
)

require (
	github.com/golang/protobuf v1.5.2 // indirect
	golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect
	golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect
	golang.org/x/text v0.3.0 // indirect
	google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
)
go.mod

λ§ˆμ°¬κ°€μ§€λ‘œ, flutter(dart) μ—μ„œ pubspec 으둜 μž„ν¬νŠΈκ°€ κ°€λŠ₯ν•˜λ„λ‘ /dartapi 디렉토리에 pubspec.yaml νŒŒμΌμ„ μΆ”κ°€ν•΄μ€€λ‹€.

name: dartapi

publish_to: 'none' # Remove this line if you wish to publish to pub.dev

environment:
  sdk: ">=2.15.0 <3.0.0"

dependencies:
  protobuf: ^2.0.1
pubspec.yaml

이제 μ„œλ²„μ™€ ν΄λΌμ΄μ–ΈνŠΈλ₯Ό 각각의 μ–Έμ–΄λ‘œ κ΅¬ν˜„ν•  μ€€λΉ„κ°€ 끝났닀. λ‹€μŒ νŽΈμ—μ„œλŠ” μ΄λ ‡κ²Œ μƒμ„±ν•œ binding code λ₯Ό μ–΄λ–»κ²Œ μž„ν¬νŠΈν•˜κ³  μ‚¬μš©ν•˜λŠ”μ§€ μ•Œμ•„λ³΄μž.