6K Views
February 29, 24
スライド概要
Connect は gRPC や gRPC と互換を持った HTTP API を作成するためのライブラリです。
これまで一般的だった公式の gRPC ライブラリを使った実装に比べ、複数プロトコルの同時サポートや型パラメーターを利用したシンプルなインターフェースなど様々なメリットを得ることができます。
私達のチームでは、新規サービスの開発で Connect を活用しています。複数プラットフォーム向けの API を一本化することで、運用の手間が少ないアーキテクチャの実現に向けた取り組みを行っています。
本登壇では、gRPC や Connect の基礎知識や概要、メリットについて説明した後、Connect を使って gRPC API を開発していく中で工夫したポイントについて実例を交えて紹介します。
DeNA が社会の技術向上に貢献するため、業務で得た知見を積極的に外部に発信する、DeNA 公式のアカウントです。DeNA エンジニアの登壇資料をお届けします。
gRPC API 開発のための Connect 活⽤法 Naoki Kishi / @p1ass © DeNA Co., Ltd.
⾃⼰紹介 岸 直輝 / @p1ass ソリューション事業本部 ● 2021年新卒⼊社 ● 新規サービスの⽴ち上げに向けた バックエンド開発やインフラ構築 ● © DeNA Co., Ltd. 好きなプログラミング⾔語は Go 2
このセッションを通じて 「Connect 良さそうだな、導⼊してみたいな」 と思ってもらえること © DeNA Co., Ltd. 3
セッションの流れ 1. Connect に⾄るまでの技術選定 2. Connect とは? 3. Connect を活⽤する上での⼯夫 4. おわりに © DeNA Co., Ltd. 4
1. Connect に⾄るまでの技術選定 © DeNA Co., Ltd. 5
私がチームに参加したときの状況 2⼈⽬のバックエンドエンジニア まだリリースされていない新規のプロダクトを開発するチーム。 これから開発を始めていくフェーズ。 バックエンドのコードは0⾏ Unity と Web ブラウザ向けの API を Go で書く。 それ以外はアーキテクチャはほとんど未決定。 © DeNA Co., Ltd. 定 術選 して ぞ いく ! 技 6
APIの通信⽅式の候補 gRPC JSON © DeNA Co., Ltd. CNCF のプロジェクトである RPC フレームワーク。 HTTP/2で通信し、シリアライズにProcotol Buffersが使われる。 シリアライズに JSON を使った、広く使われている API ⽅式。 Web API と⾔われてイメージする「ふつうの Web API」。 7
どうやって通信⽅式を決めていくか? 差別化観点 gRPC JSON パフォーマンス API仕様の書きやすさ ブラウザでの扱い 運⽤⼯数 © DeNA Co., Ltd. 8
どうやって通信⽅式を決めていくか? 差別化観点 gRPC JSON パフォーマンス ◎ ◯ API仕様の書きやすさ ブラウザでの扱い HTTP/2 の活⽤や バイナリデータを使った ⾼効率な通信 運⽤⼯数 © DeNA Co., Ltd. 9
どうやって通信⽅式を決めていくか? 差別化観点 gRPC JSON パフォーマンス ◎ ◯ API仕様の書きやすさ ◎ △ ブラウザでの扱い 運⽤⼯数 © DeNA Co., Ltd. OpenAPI のYAMLの⾏数が 増えると管理が⼤変... 10
どうやって通信⽅式を決めていくか? 差別化観点 gRPC JSON パフォーマンス ◎ ◯ API仕様の書きやすさ ◎ △ ブラウザでの扱い ? ? 運⽤⼯数 ? ? を へん の こ り 深掘 © DeNA Co., Ltd. 11
Webブラウザとの互換性 ● JSON API は不都合なく利⽤可能 ● ⼀⽅で、gRPC は HTTP/2ベースのプロトコルのため、 Web で使うには⼯夫が必要 © DeNA Co., Ltd. 12
gRPC-Web : WebブラウザのためのgRPC Web ブラウザ向けの gRPC 実装 ● Web ブラウザ向けの JavaScript クライアントライブラリ ● gRPC-Web 通信を gRPC サービスへプロキシするためのプロキシ Web ブラウザ © DeNA Co., Ltd. gRPC-Web HTTP/1.1 プロキシ (Envoy) gRPC HTTP/2 gRPC サーバー 13
gRPC-Web のつらいところ gRPC-Web のためだけのプロキシ運⽤ ● 元々リバースプロキシを⽴てる必要性がなかった ○ ● © DeNA Co., Ltd. マネージドなロードバランサーについている機能で⼗分 gRPC-Web のためだけにプロキシサーバーを⽴てる = 運⽤の負担↑ 14
ここまでの⽐較検討の結果 差別化観点 gRPC JSON パフォーマンス ◎ ◯ API仕様の書きやすさ ◎ △ ブラウザでの扱い △ ◎ 運⽤⼯数 △ ◯ gRPC の⽅が良い点が多いが、運⽤⼯数が増えるのは悩ましい... © DeNA Co., Ltd. 15
そんなところに現れた Connect < お主が求めている ものはこれか? © DeNA Co., Ltd. 16
2. Connect とは? © DeNA Co., Ltd. 17
Connect とは? ● gRPC 互換な API を作成するためのライブラリ ● Go‧Node をサポート ● 最近は iOS‧Android 向けのクライアントライブラリの提供も始まっている https://connectrpc.com/ © DeNA Co., Ltd. 18
複数プロトコルのサポート Connect Protocol ● ○ Connect 独⾃の Client A Client B Client C HTTP/1.1 ベースの API ● gRPC ● gRPC-Web プロキシが不要に! © DeNA Co., Ltd. gRPC Connect gRPC-Web Connect サーバー に ⼀⼿ る 受け 引き 19
プロトコルに依存しないインターフェイス ● 「プロトコルは詳細」という思想 ● どのプロトコルでリクエストしてきたのかを意識することなく、 サーバーの開発が可能 © DeNA Co., Ltd. 20
プロトコルに依存しないインターフェイス syntax = "proto3"; package example.v1; message GreetRequest { string name = 1; } message GreetResponse { string message = 1; } service ExampleService { rpc Greet(GreetRequest) returns (GreetResponse); } © DeNA Co., Ltd. type ExampleServiceHandler interface { Greet(context.Context, *connect.Request[examplev1.GreetRequest]) (*connect.Response[examplev1.GreetResponse], error) } ⾃動⽣成される インターフェース 21
インターフェースを満たす⾃分の実装を書く type ExampleHandler struct {} func (e ExampleHandler) Greet(ctx context.Context, req *connect.Request[examplev1.GreetRequest]) (*connect.Response[examplev1.GreetResponse], error) { name := req.Msg.GetName() res := connect.NewResponse(&examplev1.GreetResponse{Message: "Hello, " + name}) return res, nil } © DeNA Co., Ltd. が 実装 で の 依存 更なし ル 変 トコ プロ 、コード えが可能 ので り替 い 切 な ル トコ ロ プ 22
Connect採⽤の意思決定 差別化観点 Connect (gRPC) JSON パフォーマンス ◎ ◯ API仕様の書きやすさ ◎ △ ブラウザでの扱い △→◎ ◎ 運⽤⼯数 △→◯ ◯ Connect採⽤を決定 © DeNA Co., Ltd. 23
3. Connect を活⽤する上での⼯夫 © DeNA Co., Ltd. 24
認証が必要なAPIの⾃動判定 ● Protocol Buffers では、認証が必要なことを⽰すフィールドがない ● できれば API 仕様から⾃動で認証の必要性を判定したい 認証が必要な API 認証処理 各RPCごとの ハンドラー クライアント 認証が不要な API © DeNA Co., Ltd. 認証 ⾃動 の必要性 で判 断し を たい 25
完成形のイメージ func AuthenticationInterceptor() connect.UnaryInterceptorFunc { return func(next connect.UnaryFunc) connect.UnaryFunc { return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) { // TODO: ここで認証が必要なAPIかどうか判定したい if GetNeedAuthentication(req) { // 認証が必要 } else { // 認証が不要 } return next(ctx, req) } } } © DeNA Co., Ltd. 26
Custom Optionを使ってAPI仕様を書く extend google.protobuf.MethodOptions { bool need_authentication = 50001; } Custom Option を定義 service ExampleService { rpc Greet(GreetRequest) returns (GreetResponse) { option (example.v1.need_authentication) = true; } } オプション値を 設定 © DeNA Co., Ltd. 27
Connect で Custom Option の値を取得する
func GetNeedAuthentication(req connect.AnyRequest) (bool, error) {
procedures := strings.Split(req.Spec().Procedure, "/") // ["", "example.v1.ExampleService", "Greet"]
packages := strings.Split(procedures[1], ".") // ["example", "v1", "ExampleService"]
serviceName := packages[len(packages)-1] // "ExampleService"
methodName := procedures[2]
// "Greet"
reqMsg, ok := req.Any().(proto.Message)
if !ok { /* エラーハンドリング */ }
opts := reqMsg.ProtoReflect().Descriptor().ParentFile().
結構たいへん...
Services().ByName(protoreflect.Name(serviceName)).
Methods().ByName(protoreflect.Name(methodName)).
Options().(*descriptorpb.MethodOptions)
needAuthentication, ok := proto.GetExtension(opts, apiv1.E_NeedAuthentication).(bool)
if !ok { /* エラーハンドリング */ }
return needAuthentication, nil
}
© DeNA Co., Ltd.
28
Connect のアップデートでより扱いやすく! func GetNeedAuthentication(req connect.AnyRequest) (bool, error) { methodDesc, ok := req.Spec().Schema.(protoreflect.MethodDescriptor) if !ok { /* エラーハンドリング */ } opts := methodDesc.Options().(*descriptorpb.MethodOptions) メソッド情報を ⼀発で取得可能に needAuthentication, ok := proto.GetExtension(opts, apiv1.E_NeedSessionToken).(bool) if !ok { /* エラーハンドリング */ } return needAuthentication, nil } © DeNA Co., Ltd. 29
4. おわりに © DeNA Co., Ltd. 30
おわりに ● Connect は gRPC 互換な API サーバーを作成するためのライブラリ ○ gRPC-Web の弱点を補える点に魅⼒を感じた ○ 使いやすいインターフェースやエコシステムとの相性の良さは 単純なgRPC サーバーをたてる場合にもメリットになりうる ● © DeNA Co., Ltd. Custom Option を駆使した、独⾃ API 仕様の定義とその活⽤について紹介 31
最初のスライドをもう⼀度 「Connect 良さそうだな、導⼊してみたいな」 と思ってもらえること © DeNA Co., Ltd. 32