2024.12.19
システムの穴を運用でカバーしようとしてミス多発… バグが大量発生、決算が合わない状態から業務効率化を実現するまで
リンクをコピー
記事をブックマーク
松村有倫氏(以下、松村):コード生成の流れは、大まかにこんな感じです。最初に、リクエストを標準入力から読み取っていきます。読み取ったリクエストから情報を抽出して、その抽出した情報からコード生成を行います。最後に、生成したコードをレスポンスに固めて標準出力へ書き込みます。
まず、リクエストの読み取りの部分から見ていきます。リクエストは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は、見てきたとおり、活用の幅が広いツールになるので、幅広く使って、運用改善にいろいろと役立てていきたいです。
というところで、今回の発表を終わりにしたいと思います。ありがとうございました。
2024.12.20
日本の約10倍がん患者が殺到し、病院はキャパオーバー ジャパンハートが描く医療の未来と、カンボジアに新病院を作る理由
2024.12.19
12万通りの「資格の組み合わせ」の中で厳選された60の項目 532の資格を持つ林雄次氏の新刊『資格のかけ算』の見所
2024.12.16
32歳で成績最下位から1年でトップ営業になれた理由 売るテクニックよりも大事な「あり方」
2023.03.21
民間宇宙開発で高まる「飛行機とロケットの衝突」の危機...どうやって回避する?
PR | 2024.12.20
モンスター化したExcelが、ある日突然崩壊 昭和のガス工事会社を生まれ変わらせた、起死回生のノーコード活用術
2024.12.12
会議で発言しやすくなる「心理的安全性」を高めるには ファシリテーションがうまい人の3つの条件
2024.12.18
「社長以外みんな儲かる給与設計」にした理由 経営者たちが語る、優秀な人材集め・会社を発展させるためのヒント
2024.12.17
面接で「後輩を指導できなさそう」と思われる人の伝え方 歳を重ねるほど重視される経験の「ノウハウ化」
2024.12.13
ファシリテーターは「しゃべらないほうがいい」理由 入山章栄氏が語る、心理的安全性の高い場を作るポイント
2024.12.10
メールのラリー回数でわかる「評価されない人」の特徴 職場での評価を下げる行動5選
Climbers Startup JAPAN EXPO 2024 - 秋 -
2024.11.20 - 2024.11.21
『主体的なキャリア形成』を考える~資格のかけ算について〜
2024.12.07 - 2024.12.07
Startup CTO of the year 2024
2024.11.19 - 2024.11.19
社員の力を引き出す経営戦略〜ひとり一人が自ら成長する組織づくり〜
2024.11.20 - 2024.11.20
「確率思考」で未来を見通す 事業を成功に導く意思決定 ~エビデンス・ベースド・マーケティング思考の調査分析で事業に有効な予測手法とは~
2024.11.05 - 2024.11.05