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
モデルのリファクタリングが難しいと思ったら、そもそも複雑だったのはビジネス仕様だった ? / is-the-business-domain-the-real-complexity
hatsu38
0
410
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
370
Webの地図
yosuke_furukawa
PRO
6
4.8k
スマート反転とウェブアクセシビリティ
camiha
0
210
更なる可用性を求めて、5年間運用したKotlinのアプリケーションをGoでリプレイスする話
ken_tunc
0
370
スマートフォンでモールス信号を送受信する 〜スマートフォンのLEDとカメラで作る光通信の設計と実装〜
atsuki_seo
0
190
Starting & Sustaining Code-Based E2E Testing for Non-Coding QA Teams( #jasstniigata )
teyamagu
PRO
1
590
世界の中心で、AI(App Intents)をさけぶ ー App Intents中心設計の実践ガイド
touyou
0
690
[Rails World 2026] Durable orchestration on Rails: from continuation to workflow
palkan
1
330
市販E-Readerを乗っ取れ 〜Embedded Swiftで電子ペーパーガジェットを制御する〜
trickart
0
220
巨大モノリシックアプリ モダン化大作戦
ktcryomm
1
1.1k
Go × SIMDで高速化するベクトル検索 ~ルーフラインモデルでSIMDが効く境界を探れ! ~
po3rin
1
3.9k
Featured
See All Featured
RailsConf 2023
tenderlove
30
1.6k
The Illustrated Children's Guide to Kubernetes
chrisshort
51
53k
AI: The stuff that nobody shows you
jnunemaker
PRO
10
1.1k
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
68
58k
Building Experiences: Design Systems, User Experience, and Full Site Editing
marktimemedia
1
610
The Impact of AI in SEO - AI Overviews June 2024 Edition
aleyda
6
1.2k
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
720
Practical Orchestrator
shlominoach
192
12k
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
3
1.3k
First, design no harm
axbom
PRO
2
1.3k
Noah Learner - AI + Me: how we built a GSC Bulk Export data pipeline
techseoconnect
PRO
0
440
Jess Joyce - The Pitfalls of Following Frameworks
techseoconnect
PRO
1
420
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