Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Goでprotocプラグイン作った話 DMM.go #3
Search
Arimichi Matsumura
July 26, 2021
Programming
1.8k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Goでprotocプラグイン作った話 DMM.go #3
Arimichi Matsumura
July 26, 2021
Other Decks in Programming
See All in Programming
SONY CISC-NEWS NWS-1750 + NWB-225 フレームバッファの NetBSD/news68k ドライバ実装 / OSC2026Hiroshima
tsutsui
0
150
AGENTS.md Is Not Enough:Build Skills, Don't Download Them
lx_t
0
130
Snowflakeで業務アプリを作ろう。 Snowflakeのアプリ機能解説&実践ガイド
ayumu_yamaguchi
2
300
AI が書く Go コードの品質を劇的に向上させる Linter: “declscope”
mpyw
0
440
技術的負債の返済は、AI時代の複利で効く投資 — 経営としての意思決定とその遂行
curekoshimizu
0
1.9k
UnityでSystem.Net.WebSocketsなWebSocketサーバが動かないのでUnity Monoのコードを覗いてみた / about implementing websocket server with unity mono
drumath2237
1
430
モデルのリファクタリングが難しいと思ったら、そもそも複雑だったのはビジネス仕様だった ? / is-the-business-domain-the-real-complexity
hatsu38
0
410
MCP Appsでつくるチャット駆動な地図アプリ
nakamori1024
0
110
SREの越境 / SRE Collaboration
y0hgi
2
290
re:Inventに行く前に知っておきたい現地参加のノウハウ
nokomoro3
0
170
Apple Intelligence を用いた個人情報誤送信防止、及びユーザーリクエスト体験の改善について
yukiny
0
190
JPUG勉強会 OSSデータベースの内部構造を理解しよう(第2回)
oga5
0
280
Featured
See All Featured
The Organizational Zoo: Understanding Human Behavior Agility Through Metaphoric Constructive Conversations (based on the works of Arthur Shelley, Ph.D)
kimpetersen
PRO
0
480
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
A Soul's Torment
seathinner
8
3.6k
Designing for humans not robots
tammielis
254
26k
Balancing Empowerment & Direction
lara
6
1.3k
KATA
mclloyd
PRO
35
16k
Fashionably flexible responsive web design (full day workshop)
malarkey
409
67k
Groundhog Day: Seeking Process in Gaming for Health
codingconduct
0
370
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.6k
Making the Leap to Tech Lead
cromwellryan
135
10k
YesSQL, Process and Tooling at Scale
rocio
174
15k
Leo the Paperboy
mayatellez
10
2.3k
Transcript
© DMM.com Goでprotocプラグイン 作った話 DMM.go #3 DMM.com プラットフォーム事業本部 メンバーシップ開発部 基盤開発グループ
松村 有倫
© DMM.com 自己紹介 松村 有倫(まつむらありみち) • 20新卒入社 • プラットフォーム事業本部 •
基盤開発グループ • Go歴は1年くらい • Go製プロダクトの保守 • プロキシやツールの実装
© DMM.com 今日のお話 • 運用自動化のためのツールをGoで自作した話 • Protocol Buffersのスキーマ定義からGoのコード生成を行った • コード生成に関連した話題
• GoでGoのコード生成をする時の話
© DMM.com 背景 Goでprotocプラグインを自作して運用自動化
© DMM.com 基盤開発グループ • プラットフォーム事業本部(PF)では各事業共通で使われる 種々のWebAPIを提供している(会員基盤・決済基盤 etc.) • 基盤開発グループはそれらのAPIの「横断処理」の部分を担当 •
API Gateway(Go製) • 認可API • この他PF全体のAPI改善に向けた取り組みも行っている • PF内のDDD推進など
© DMM.com API定義の管理 • API-Gatewayが提供するAPI定義の管理基盤が欲しい • APIの追加時にレビューして品質を高めたい • 管理したデータを活用して各種の自動化を行いたい Protocol
Buffers + Git で基盤構築
© DMM.com Protocol Buffers • Google製の通信データのシリアライズフォーマット • プログラミング言語非依存 • 専用のInterface
Definition Language(IDL)で通信スキーマを定義 service Ping { rpc Ping (PingReq) returns (PingResp); } message PingReq { string path_id = 1; string query_id = 2; } message PingResp { string id = 1; }
© DMM.com Protocol Buffersの採用 • スキーマ記述言語としてのIDLが使いやすい • 部分構造を分けて記述できたり、簡素で読みやすい • コンパイラ(protoc)によって幅広くスキーマ定義を活用できる
• スキーマ定義からgRPC実装やドキュメントを生成できる • プラグインで拡張すれば他にも様々なコード生成が可能 ➡ API管理のためのフォーマットとして優秀
© DMM.com Protocol Buffersの採用 • スキーマ記述言語としてのIDLが使いやすい • 部分構造を分けて記述できたり、簡素で読みやすい • コンパイラ(protoc)によって幅広くスキーマ定義を活用できる
• スキーマ定義からgRPC実装やドキュメントを生成できる • プラグインで拡張すれば他にも様々なコード生成が可能 ➡ API管理のためのフォーマットとして優秀 この部分の例の話
© DMM.com rest-frontend • rest-frontend:API-Gateway用のREST/gRPC変換プロキシ • バックエンドはREST / gRPC両系統をサポートしたい •
API-Gatewayからは共通してRESTで提供したい API Gateway Client Backend Service (gRPC) Backend Service (REST) rest-frontend REST REST REST gRPC
© DMM.com GitHub.「grpc-ecosystem/grpc-gateway: gRPC to JSON proxy generator following the
gRPC HTTP spec」. https://github.com/grpc-ecosystem/grpc-gateway,(参照 2021-07-06). rest-frontendのコード生成 • rest-frontend用のコードは grpc-gatewayで生成 • protobufからサービス定義ごとに 変換プロキシのGo実装を生成
© DMM.com 生成されたコードの利用 func run() error { ... opts :=
[]grpc.DialOption{grpc.WithInsecure()} // オプション mux := runtime.NewServeMux() // grpc-gateway用ルーター生成 // ホストを指定してサービスごとに生成されたプロキシをマウント err := gw.RegisterFooHandlerFromEndpoint(ctx, mux, "foo.example.com", opts) if err != nil { return err } err := gw.RegisterBarHandlerFromEndpoint(ctx, mux, "bar.example.com", opts) ... return http.ListenAndServe(":8081", mux) // http.Handlerとしてlisten }
© DMM.com 生成されたコードの利用 func run() error { ... opts :=
[]grpc.DialOption{grpc.WithInsecure()} // オプション mux := runtime.NewServeMux() // grpc-gateway用ルーター生成 // ホストを指定してサービスごとに生成されたプロキシをマウント err := gw.RegisterFooHandlerFromEndpoint(ctx, mux, "foo.example.com", opts) if err != nil { return err } err := gw.RegisterBarHandlerFromEndpoint(ctx, mux, "bar.example.com", opts) ... return http.ListenAndServe(":8081", mux) // http.Handlerとしてlisten } ホストを指定してルーターに 埋め込む部分は手作業
© DMM.com 運用自動化 • ルーティング設定を自動化したい • ホストの設定は別で管理して注入できるようにしたい • Gatewayに新規サービスが追加されるたびに作業するのは面倒 protocプラグインで自動生成
© DMM.com protocプラグイン Goでprotocプラグインを自作して運用自動化
© DMM.com プラグインの作り • プラグインの実体はprotocと標準入出力で通信する実行形式 • 通信フォーマットはなんとprotobuf • protobufを解釈できれば実装に使う言語は自由(今回はGo)
© DMM.com 使うライブラリ google.golang.org/protobufモジュールを利用する • proto • Goからprotobufの各種処理を行うためのライブラリ • types/pluginpb
• protoc・プラグイン間の通信のためのメッセージ型 • types/descriptorpb • protoファイル情報を記述したメッセージ型
© DMM.com コード生成の流れ • リクエストを標準入力から読み取る • リクエストから情報抽出 • 抽出した情報からコード生成 •
レスポンスを標準出力へ書き込む
© DMM.com リクエスト読み取り // protocからのファイル生成リクエストを読み取る func parseReq() (*pluginpb.CodeGeneratorRequest, error) {
// 標準入力から読み込み buf, err := io.ReadAll(os.Stdin) if err != nil { return nil, err } // proto.UnmarshalでGoの型にデコード var req pluginpb.CodeGeneratorRequest if err = proto.Unmarshal(buf, &req); err != nil { return nil, err } return &req, nil }
© DMM.com 情報抽出 // pluginpb.CodeGeneratorRequestの定義 // https://pkg.go.dev/google.golang.org/protobuf/types/pluginpb より // スライドの都合で一部編集
type CodeGeneratorRequest struct { // コード生成の対象となっているprotoファイル FileToGenerate []string // プラグインに渡される引数 Parameter *string // リクエストに含まれる全てのprotoファイル情報 ProtoFile []*descriptorpb.FileDescriptorProto // protocのバージョン CompilerVersion *Version } FileToGenerate + Protofile → 全サービス定義 Parameter →ホスト情報のyamlファイル名
© DMM.com コード生成 • コード生成はGo標準のtemplateパッケージを利用 • 渡した値を処理してテンプレートからテキスト生成 import ( {{range
.Imports}}{{printf "\t%s %q\n" .Alias .ImportPath}}{{end}} ) import ( foo “git.dmm.com/yourorg/yourrepo/gen/go/foo” bar “git.dmm.com/yourorg/yourrepo/gen/go/bar” ) テンプレート 生成テキスト
© DMM.com レスポンス書き込み // protocへ生成ファイル情報を返す func emitResp(resp *pluginpb.CodeGeneratorResponse) { //
proto.Marshalでprotobufにエンコード buf, err := proto.Marshal(resp) if err != nil { log.Fatal(err) } // 標準出力に書き込み if _, err = os.Stdout.Write(buf); err != nil { log.Fatal(err) } }
© DMM.com プラグインの利用方法 • 作ったプラグインを実行形式にビルド • ”protoc-gen-”から始まる名前にしてパスを通す • protocから”--xxx_out”オプションをつけてプラグイン呼び出し •
“xxx” はプラグイン名の ”protoc-gen-” 以降の部分 • ”protoc-gen-” + “xxx” で呼び出されるプラグインが決まる # protoc-gen-grpc-gateway-muxの呼び出し protoc -I proto/ \ # protoファイルのimportパス --grpc-gateway-mux_out=. \ # 生成先ディレクトリ --grpc-gateway-mux_opt=config_file=routing.yml \ # プラグイン引数 *.proto # コード生成対象のprotoファイル
© DMM.com コード生成で気をつけたこと • コレクションに対するコード生成 • Code generatedコメント • フォーマッタの適用
© DMM.com コレクションに対するコード生成 • 生成のたびに要素の順序が大きく変わると差分の管理が面倒 • 生成時に利用するコレクションはスライスで順序を管理する • ソートしてツールの側で順序が一定になるように保証する •
Goはmapのイテレーション順序がランダムなので注意 // コード生成用のデータ type registry struct { MuxPackage string // 生成するGoコードのpackage名 ProtoFiles []string // ソースとなったprotoファイル一覧 Imports []goImport // Goコードのimport Services []service // serviceに関する情報 }
© DMM.com Code generatedコメント • 生成されたコードであることを明示するためのコメント • 公式に仕様がある • 以下の正規表現にマッチするものをコードの先頭につける
• 今回のコード生成でつけたコメント: • ツールなどが生成ファイルを識別する手段なのでつけておく ^// Code generated .* DO NOT EDIT\.$ // Code generated by protoc-gen-grpc-gateway-mux. DO NOT EDIT.
© DMM.com フォーマッタの適用 • 生成したコードに対してフォーマッタをかけておく • GoコードのフォーマッタはGoから呼べる ⇨ format.Source() •
細かなインデントの調整などをしなくて済む • 今回だとGoコードにおけるimportの重複排除もコレでサボっている var buf bytes.Buffer // コード生成先buffer ... // コード生成 ... out, err := format.Source(buf.Bytes()) // 生成コードをフォーマット
© DMM.com Before / After ルーター作成部分の処理を生成コードに切り出せた! func run() error {
... - mux := runtime.NewServeMux() - err := gw.RegisterFooHandlerFromEndpoint(ctx, mux, "foo.example.com", opts) - if err != nil { - return err - } - err := gw.RegisterBarHandlerFromEndpoint(ctx, mux, "bar.example.com", opts) - ... + mux, err := mux.New(ctx, opts) ... }
© DMM.com 展望・まとめ Goでprotocプラグインを自作して運用自動化
© DMM.com Goとコード生成 • GoでGoのコードを生成するツールは揃っている • テンプレートエンジン・フォーマッタが標準ライブラリにある • 単独の実行形式をビルドしやすい点でもGoはツール向き •
protocプラグインを作る上でもこの点はありがたかった • コード生成をうまく活用すればGoの表現力を補強できる • goa:DSLからWebAPIのIO部に関するコード生成 • sqlboiler:DBスキーマからORMの実装を生成
© DMM.com 現状 • まだ実運用には乗っていない • gRPCのバックエンドがまだ出てきていない • 良い感じの運用プロトタイプはできた •
Github API + CircleCIで自動化 • protobufの更新にフックしてコード生成 + rest-frontend更新
© DMM.com さらなる展望 • protobufにはカスタムオプションという仕組みがある • スキーマ定義にドメイン固有の独自情報を追加できる • APIスキーマ以外にも様々なデータを管理できるかも •
API-Gatewayのタイムアウト / 認可で使うURN etc. • 管理できる情報が増えれば自動化の幅を増せる • Gatewayの設定ファイルを自動生成 etc.
© DMM.com まとめ • protocプラグインを自作して運用を自動化しました • Goを使ったGoのコード生成の例を紹介しました • protobufの活用の幅は広いので色々運用改善に役立てたい
© DMM.com 参考資料 今さらProtocol Buffersと、手に馴染む道具の話 https://qiita.com/yugui/items/160737021d25d761b353 protocプラグインの書き方 https://qiita.com/yugui/items/87d00d77dee159e74886 go generate
のベストプラクティス https://qiita.com/yaegashi/items/d1fd9f7d0c75b2bb7446