
2025.08.01
災害大国・日本に求められる“命しか守れない防災”からの脱却 最長2週間先の気象災害予測による対応策
リンクをコピー
記事をブックマーク
松村有倫氏(以下、松村):コード生成の流れは、大まかにこんな感じです。最初に、リクエストを標準入力から読み取っていきます。読み取ったリクエストから情報を抽出して、その抽出した情報からコード生成を行います。最後に、生成したコードをレスポンスに固めて標準出力へ書き込みます。
まず、リクエストの読み取りの部分から見ていきます。リクエストはpluginpb.CodeGeneratorRequestという型で管理します。その後の流れですが、まずprotocからのリクエストを標準入力から読み取って、このprotobuf形式の標準入力をproto.Unmarshalという関数でGoの型にデコードします。こうすることで、標準入力からpluginpb.CodeGeneratorRequestを読み出せます。
リクエストが読み出せたら、今度はそのリクエストから情報の抽出を行っていきます。先ほどのCodeGeneratorRequestの中身がこちらです。
コード生成の対象になっているprotoファイルだったり、プラグインに渡される引数だったり、リクエストに含まれるすべてのprotoファイル情報だったり、protocのバージョンだったりが入っています。
今回はこの、FileToGenerateと、ProtoFileから、ProtoFileに含まれているすべてのサービス定義を抜き出して、ここのParameterから、ホスト情報を記述するYAMLのファイル名を取り出します。
リクエストから情報の抽出ができたら、今度は抽出した情報をもとにコードの生成を行います。このコード生成ですが、今回はGo標準のtemplateパッケージを利用しています。
このパッケージは、渡した値を処理して、テンプレートからテキストを生成できる、テンプレートエンジンのライブラリです。
(スライドを示して)下の例が、Goのimportに関する部分のコード生成ですが、例えばImportsという、Goのimport情報が入ったフィールドを「.Imports」で読み出して、それに対してrangeと書くと、Importsの各要素に対して、ループを回せます。
その中で、このテンプレートは各要素のAliasというフィールドと、ImportPathというフィールドを読み取り、printfというコマンドを呼び出して、それぞれ出力します。こうして定義したテンプレートに値を渡すことで、下のようなimportの部分に関するコード生成を実際に行えます。
最後に、生成したコードをprotocで返す部分の処理です。レスポンスはpluginpb.CodeGeneratorResponseという型で管理しています。
まず、この型の値をproto.Marshalという関数で、protobufの形式にエンコードします。そのあとで、エンコードした内容を標準出力に書き込むことで、protocにレスポンスを返します。
プラグインの利用方法です。実装したプラグインは、実行形式にビルドして使います。この時、「protoc-gen-」から始まる名前にしてパスを通します。
最後、protocから、「--xxx_out」というオプションを付けることにより、プラグインを呼び出せます。この「xxx」の部分には、プラグイン名の「protoc-gen-」以降の部分が入ります。というのも、protocは、「protoc-gen-」の部分と「xxx」を組み合わせて、呼び出されるプラグインが決まる仕様になっているからです。
(スライドを示して)例えば、今回作った「protoc-gen-grpc-gateway-mux」というツール。こういうプラグインの場合は、「grpc-gateway-mux」の部分を抜き出してきて、「grpc-gateway-mux_out」というオプションを付けることで、プラグインを呼び出せます。
このオプションは、コード生成の生成先ディレクトリを指定します。この「_out」以外にも、「_opt」というオプションがあって、今度はこれを使うことでプラグインに引数を渡せます。
次に、コード生成で気をつけたことを少し紹介します。コレクションに対するコード生成、Code generatedコメント、フォーマッターの適用の3つを話したいと思います。
まず1つ目です。コレクションに対するコード生成です。コレクションに対してコードを生成する時に、生成するたびに要素の順序が大きく変わってしまうと、差分の管理が面倒くさくなってしまいます。
これを回避するために、生成時に利用するコレクションは、スライスで順序つきで管理して、ソートしてツールの側で順序が一定になるように保証します。
例えば今回の例だと、「ソースとなったprotoファイル一覧」や「Goのimport」、「serviceに関する情報」などのコード生成用のデータを、全部スライスで管理して、順序をツールの側で持って管理しています。
Goの場合は、特にmapのイテレーションの順序がランダムになってしまいます。このあたりのコレクションをmapで管理してしまうと、毎回生成のたびに順序がランダムになり、差分の管理が面倒くさくなってしまうので、注意が必要になります。
次に、Code generatedコメントについて。これはなにかというと、生成されたコードであることを明示するためのコメントです。正規表現にマッチするコメントが先頭に付いたファイルは、「生成されたコードですよ」とする、という仕様が公式にあります。
(スライドを示して)今回だと、作ったツール名をコメントの中に入れて、こんな感じのコメントを付けています。
このコメントは、linterなどのツールが「生成したコメントは対象外にする」ように、ツールが生成ファイルを識別するための手段になるので、必ず付けるようにしましょう。
最後に、フォーマッターの適用です。生成したコードに対して、フォーマッターをかけています。今回は、Goのコード生成をGoから行いますが、Goのフォーマッターは、実はGoでAPIが提供されていて、format.Sourceという関数で、フォーマッターをかけることができます。
これをしておくと、細かなインデントの調整などをしなくて済むので、非常に便利です。
また、これはGo特有の事情になりますが、importの重複排除などもフォーマッターでやってくれるので、今回は、生成コードにおけるimportの重複排除などの処理もサボっています。
そんなこんなでプラグインができまして、無事にルーター作成部分のコードを生成コードに切り出せました。サービスごとにホストを指定して、プロキシを埋め込む部分が全部自動生成になったので、外側から情報を管理して、いちいち作業することなく、自動で作業を行えるようになりました。
展望とまとめについて話していきたいと思います。
まず、「Goとコード生成」について。今回GoでGoのコードを生成してみたところ、テンプレートエンジンやフォーマッターがけっこう標準ライブラリにあって、実装は非常にしやすかった印象を受けました。
それから、Goだと単独の実行形式をビルドしやすいと思いますが、こういう点でもGoはツール向きなのだと少し感じました。
最終的には、protocプラグインで単独の実行形式としてビルドをする必要がありますが、この性質はprotocプラグインを作るうえでも非常にありがたかったです。
あと、コード生成をうまく活用すると、Goの表現力を補強できることをあらためて感じました。
世の中を見回してみても、「goa」だったり「sqlboiler」だったり、コード生成をうまく利用しているライブラリはたくさんあると思いますが、やはりGoにとってもコード生成は大切な技術なのだとあらためて実感しました。
現状ですが、gRPCのバックエンドがまだ出てきていないので、実運用に乗っているものではありません。
ただし、運用のプロトタイプとしてはけっこういいものができていて、GitHub APIとCircleCIで自動化して、protobufの更新にフックして、コード生成とrest-frontendの更新を行うフローを組むことはできています。
さらなる展望ですが、protobufにはカスタムオプションという仕組みがあります。これはなにかというと、スキーマ定義を拡張して、ドメイン固有の独自情報を追加できる仕組みです。
これを使うと、APIスキーマ以外にも、例えばAPI-Gatewayのタイムアウトであったり、認可で使うURNだったり、さまざまなデータを管理できるかもなと考えています。今後の展望として、追加・管理できる情報が増えれば、自動化の幅を増やせると思っています。
例えばこれらの情報を載せて、protobufからGatewayの設定ファイルを自動生成できるかもしれないと考えていて、現在はこのあたりを考えて進めています。
最後にまとめです。今回は、protocプラグインを自作して、運用を自動化した話をしました。また、Goを使ったGoのコード生成についても紹介しました。
protobufは、見てきたとおり、活用の幅が広いツールになるので、幅広く使って、運用改善にいろいろと役立てていきたいです。
というところで、今回の発表を終わりにしたいと思います。ありがとうございました。
続きを読むには会員登録
(無料)が必要です。
会員登録していただくと、すべての記事が制限なく閲覧でき、
スピーカーフォローや記事のブックマークなど、便利な機能がご利用いただけます。
すでに会員の方はこちらからログイン
名刺アプリ「Eight」をご利用中の方は
こちらを読み込むだけで、すぐに記事が読めます!
スマホで読み込んで
ログインまたは登録作業をスキップ
2025.09.17
“仕事が遅い人”が会議でやりがちなNG行動 北の達人・木下勝寿氏が教える効率的な打ち合わせ術
2025.09.16
日本人が英語学習で苦戦する根本的原因 「言いたいことの順番」が真逆になる英語と日本語
2025.09.18
「どうしたら英語が上達するか」は日本人特有の悩み 中国人・韓国人との“学ぶ姿勢”の違い
2025.09.17
英語ネイティブは「would」をどう使っているか? 「Do you like〜」と「Would you like〜」の違い
2025.09.16
“できる仕事のキャパが10倍になった” 東証上場社長を変えた習慣「ピッパの法則」の効果
2025.09.17
仕事に雑談は必要なのか? 重要性・効果と、社員同士が話しやすくなるコツを紹介
2025.09.22
「納得しないと動かない」困った部下の対処法 メンバーの主体性を引き出す伝え方
2025.09.10
人生の差は20代で決まる “指示待ち人間”で終わらないために積むべき4つの経験
2025.09.19
もっとも大変な管理職は“プレイング課長” それでもマネジメントを「罰ゲーム」と考えるべきでない理由
2025.09.17
“上司と合わない”ストレスから抜け出すために 会社を辞める前に実践したい思考法
START CAMP 2025
2025.07.18 - 2025.07.19
「リーダー1年目のマネジメント大全」刊行一周年トークイベント ~「優れたリーダー」を大解剖する夜~
2025.07.11 - 2025.07.11
管理職は罰ゲームではなかった!マネジメントスキル、リーダーシップは財産に!
2025.07.31 - 2025.07.31
厚生労働省「健康づくりのための睡眠ガイド2023」の産業現場での活かし方と国の健康睡眠施策トレンド
2025.09.01 - 2025.09.01
後回しを断ち切り“すぐやる人”になる最速メソッド|東証上場社長実践の後回し撲滅法
2025.06.24 - 2025.06.24