2019の23日目の記事です。Divisionに所属している清水(@budougumi0617)です。23日目の本記事では、レイヤードアーキテクチャを採用している上で頻出するであろうボイラープレートの悩みを共有します。Go言語(以下Go)でコードを自動生成するためのツールを作った話と、利用したtext/templateパッケージなどの公式パッケージの概要に触れます。自動生成時に利用するテンプレートはGoのtext/templateパッケージを利用しますが、テンプレートファイルは自由に変更できるので、将来的にはGo以外のコードも生成する予定です。BANK(エールバンク)」という即時に資金調達ができる金融サービスを提供しています。BANK(エールバンク)」というサービスはGo製のサーバとPython製のサーバを組み合わせたバックエンド構成で提供されています。Go製のサーバに関しても、Python製のサーバに関してもウェブアプリケーションフレームワーク(WAF)を用いていないため、実装はレイヤードアーキテクチャに則ったコード、ディレクトリ構成となっています。BANK同僚の東口さん(@hgsgtk)の登壇資料をご参照ください。このようなレイヤードアーキテクチャによる設計アプローチは、言語やWAF特有な構成に依存しないため、Goで実装されていても、Pythonで実装されていてもディレクトリ構成自体はほぼ同じです。適切に分割された各コンポーネントのコードが担う責務はシンプルな単一責務であるため、プロダクトに途中から参加したような私でも比較的簡単にコードを理解することができました。レイヤードアーキテクチャやクリーンアーキテクチャというアプローチを取ったとき問題になるのが、似たような構造のコードを大量に生成する必要が発生することです。レイヤードアーキテクチャとボイラープレートレイヤードアーキテクチャやクリーンアーキテクチャに基づいて実装を行なっていくと、ひとつのユースケースに対して大量のファイルを作成する必要が発生します。「ユーザーを登録する」というような機能を作成する場合、我々のプロダクトでは、最低でも7ファイルを作成する必要があります。create_user_service_test.goクリーンアーキテクチャやレイヤードアーキテクチャに詳しい方ならば、なんとなくファイル内容は想像できるかと思いますが、一部を記載するとこんなカタチです。UserRegisterService}このように、ひとつのリクエストを処理するために複数の実装をする必要があります。上記ではユーザー登録用のコードを例にしましたが、ユーザー削除を行う際には(modelコードを除いて)上記の例のように同じ構成のファイルの組み合わせを「新たに」実装します。ここで、新たに作るユーザー削除用のコード群は各構造体の引数やメソッドの処理内容は違えど、ファイルを構成する要素はユーザー登録時とほとんど同じになります。以上の実装方針に則ると、ひとつのレイヤー(ディレクトリ)の中には上記のようなコードのファイルが大量に増えていきます。......同様の設計アプローチを取られている方ならば、同様の悩みをもったことがあるのではないでしょうか。似た構造体をつくることになるので、私は作成済みのエンドポイントのファイルをコピーして再利用可能な部分だけ残してから実装を行なっていました。エンジニアの三大美徳(怠惰(怠慢)、短気、傲慢)に反してますね。bakeコマンドのようなWAFのコード自動生成機能を使えるならばスケルトンコードをすぐに生成できるでしょう。いうことで、今回は与えた「モデル名」と「アクション名」、「テンプレートファイル」に基づいて各レイヤーのボイラープレートを自動生成するためのCLIツールを作りました。lgen作成したCLIツールは以下になります。レイヤードアーキテクチャの実装はプロダクトやチームによっても微妙に差異があります。そこでこのツールは以下のような特徴を持ちます。実行オプションに渡されたモデル名とアクション名を使ってコードを自動生成する生成するコードは指定されたディレクトリ配下にあるファイルを読み込んで決めるディレクトリ構成もコピーすることで、各プロダクトのレイヤー構成に対応するテンプレートやディレクトリ構成を自由に設定できるので、様々なレイヤー構造に柔軟に対応できるざっくり使い方を書くと、以下のようになります。最初に、後述するtext/templateパッケージを使ってアクション名とモデル名を変数としたこのようなボイラープレートの雛形を書いておきます。}}次に、自分のプロダクトのレイヤー(ディレクトリ)構造に合わせてそれぞれのファイルを配置します。usecase_test.goそしてモデル名とアクション名などを指定してツールを実行します。次の例では、GetUser操作に関するコードをtemplatesディレクトリ配下のレイヤー構造とテンプレートを使って、myproductディレクトリ配下に自動生成します。myproduct実行すると、以下のようにtemplatesディレクトリと同じディレクトリ構造の場所にファイルが生成されます。get_user_usecase_test.go最初に書いたボイラープレートの中身は、モデル名とアクション名が展開されて以下のようなコードが生成されました。}}サンプルではテンプレートを数個しか用意しませんでしたが、これに加えて他のレイヤーのコード、対になるテストコードもテンプレートさえ用意すれば全て自動生成することができます。この結果、いままで新しいロジックを書くときにコピペと置換を繰り返していた時間を削減できます。これで2020年はロジックに集中してコードを書けそうです。ツールだけでの紹介では終わってしまうので味気ないので、利用しているGoの標準パッケージについて紹介しておきます。text/templateパッケージGoにおいて、コードの自動生成ツールをつくるのならば、text/templateパッケージ1を使うことになるでしょう2。このパッケージは名前の通りテンプレートエンジンの機能を提供しています。このパッケージを直接利用することはないかもしれませんが、Go製のOSSでそのままtext/templateパッケージの仕組みが使われていることも多いです。KubernetesエコシステムのひとつでGoで作られているHelmのChartの書き方もtext/templateパッケージそのままです。よって、直接利用することはなくともtext/templateパッケージの記法を覚えて損はないと思います。text/templateパッケージの使い方はGoDocの該当パッケージの説明を読むのが一番です。使い方を書くと次のようになります。john.doe@example.combufは変数などを展開したあとの最終結果を格納するための変数です。tmplがテンプレート文字列です。.Name}}などは「Executeメソッドの引数で受け取った構造体のNameフィールドをここに出力する」という意味になります。paramsはtmplテンプレートに埋め込むための構造体です。if文ではsamplesという名前でtmplテンプレート文字列からテンプレートを作り、params構造体を使ってテンプレートを展開する。いう処理をしています。}}というような記述をしていました。これは変数の値を出力前に関数にパイプして加工することができる機能です。map[string]interface{}が実体であるtemplate.FuncMapにマッピングしたあと、Executeメソッドを呼ぶ前にParseメソッドを使うことでテンプレート内で利用することが可能になります。今回自作したツールの場合はstringsパッケージのstrings.Title関数をtitleとして登録しています。err}もちろん自作関数を登録しておくことも可能です。文字列長を返すlenなどは既定で登録済みだったりします。その他、if文やrangeなどの制御構文もテンプレート内で利用することができます。詳細はtext/templateパッケージのGoDoc冒頭をご覧ください。-}}といったコメントを入れておきます。することで、テンプレートファイル中でも構造体のフィールドに対する補完が有効になります。今回自作したツールでは、コードの自動生成をする上で、指定されたディレクトリにあるテンプレートファイル群を利用します。Goで「あるディレクトリの配下にあるディレクトリ、ファイルを使った処理」を書くには、path/filepath.Walk関数を使います。他にも相対パスを絶対パスに変換するなどの処理があるのですが、ファイル・ディレクトリ処理に関しては@mattnさんがまとめてくださっている記事を見ればほぼ完結するので、ここでは説明を省略させていただきます。今回は使っていないのですが、(上記記事が公開されたあとにリリースされた)Go1.12やGo1.13で、実行ユーザーのホームディレクトリを返すos.UserConfigDir関数やos.UserHomeDir関数も追加されています。おわりに今回はレイヤードアーキテクチャを使ってGoの実装をする際の悩みの共有をし、課題解決のためにコードを自動生成するコマンドラインツールを実装した話を共有しました。みなさんのプロジェクトでどのようにコードのボイラープレート問題を解決しているのかも聞けたら幸いです。コマンドラインツールを作る上で役に立つtext/templateやfile/filepathパッケージの紹介をしました。Goでコマンドラインツールを作ればクロスプラットフォーム対応が簡単にできます。みなさんもぜひ自作コマンドラインツールを作ってみてください。「こういう機能があるならlgenを使ってもいいんだけどなあ」みたいな要望があればissueを立ててもらうか、Twitterで私にメンションを飛ばしてもらえると嬉しいです。Productの神宮司さんと、基盤グループのめもり〜さんです。最後に今回の記事・ツールの作成時に参照した情報を再掲しておきます。今回は触れませんが、HTML生成用のテンプレートエンジンとしてはhtml/templateパッケージもあります。↩Goでコードの自動生成というと、gogenerateもありますが、今回は用途が違うので触れません。)の開発チームによるブログです。Groupの大木です。Developerをしている東口です。Groupのマネージャーをしている山田です。12/1から始まっ...Archiveスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。