ボイラープレートを一括生成するコマンドを作る
APIサーバーの開発をGoで行っていて、アーキテクチャとしてクリーンアーキテクチャに沿った形で設計した。
責務が分かれたり、レイヤー毎のユニットテストがやりやすくなったのはいいのだが、新しいエンドポイントを追加する際に追加しなければならない記述が多いのが非常に不満だった。
controllers, database, interactors, repository, entities…
エンドポイント追加のたびにこれらにコンストラクタだったりDIだったりを書いていくのは非常にだるい
退屈なことはプログラムにやらせよう、ということでこれらのボイラープレートを自動で一括で生成できる君を作ったのでまとめておく。
クリーンアーキテクチャでエンドポイントを追加する際には各レイヤーに記述を書かなければならず、最低限以下のようなファイルと記述が必要になる。
・controllers: コンストラクタ、interactorsの呼び出し
・database: コンストラクタ、実際のDB処理
・interactors: コンストラクタ、アプリケーションロジック
・repository: databaseのinterface
もちろん処理内容によってはドメインロジックが必要だったり、リクエストパラメータのbind処理が必要になったりするが、それを抜きにしてもこれだけある。
やってられない。毎回0からこれらを記述するのはだるいのでこれらを一括生成したい。
もちろんアプリケーションロジックなどは処理内容によって大きく変わるが、コンストラクタなどの雛形は一括生成することの恩恵を受けられるはず。
イメージとしては以下のようなコマンドを打つと、適切なファイル名で最低限必要なコードが書かれたものを一括生成する。
このコマンドを作るにあたってのポイントは、以下のように命名規則を揃えること。
・ファイル名はsnake_case
・ファイル名に応じて書かれる変数名はUpperCamelCase
もしくはlowerCamelCase
(Goのスコープを適切に設定するため)
今回はコマンドラインの引数としてファイル名のsnake_case
を受け取って、ファイル内の記述で使うUpperCamelCase
とlowerCamelCase
はsnake_case
から変換することとした。
目的のことができそうなライブラリを色々探してて最初に見つかったのがwireというライブラリ。
GitHub - google/wire: Compile-time Dependency Injection for GoCompile-time Dependency Injection for Go. Contribute to google/wire development by creating an account on GitHub.github.com
DIライブラリでわりと人気があるものだが、いわゆるDIコンテナとはちょっと違うらしい。
そもそもDIコンテナのある言語をちゃんと触ったことがないのでこの辺の違いはわかってない。
このライブラリで目的が達成できるか考えてみたが、結論厳しそう。
ちゃんと理解できてるか怪しいが、こういったツールはあくまでDIにおける組み立て部分をよしなにやってくれるものであって、組み立てに必要なパーツは自分で定義する必要があるという認識でいる。
今回やりたいのはそのパーツを自動生成したいという話なので目的にマッチしないかなと思った。
もう少し調べてみると、標準ライブラリにtext/template
なるパッケージがあるらしい。
引数を渡すとその引数を埋め込んだ文字列を出力してくれるとのこと。
Go標準のテンプレートエンジンtext/templateを使ってみる - CLOVER🍀これは、なにをしたくて書いたもの? Goのテンプレートエンジンを調べてみようかなと思ったのですが、標準ライブラリにあるようなので、こちらを試して みることにしました。 Goの標準ライブラリにあるテンプレートエンジン text/templateと、html/templateの2種類があるようです。 template - The Go Programming Language template - The Go Programming Language 名前から想像はつきますが、text/templateはテキスト生成のためのテンプレートエンジンで、html/templateはHTML生成のための…kazuhira-r.hatenablog.com
これだ…!!!
ファイル名を引数として渡して、それを変数名に変換した上で文字列として埋め込んだファイルを出力すればいけそう。
というわけでやってみた。
text/template
の基本的な使い方を見た後に、実際に目的のファイルを生成するという順で見ていく。
10行目のようなtemplateの文字列を用意しておいて、12行目で*template.Template
を作成する。
16行目でt.Execute
とし、templateにWorld
を埋め込んだ文字列を標準出力に出すようにしている。
template中の{{.}}
の部分にt.Execute
で渡した引数の文字列が埋め込まれるようになっている。
t.Execute
の第一引数はio.Writer
になっているので、ここを標準出力じゃなくてファイルにすればファイルに出力できそう。
*template.Template
作成時にも別で用意したファイルをパースすることができるっぽい。
これらを組み合わせてファイル名の文字列を引数として渡すと目的の記述が書かれたファイルを生成する処理を書いてみる。
具体的な記述に入る前に全体的なディレクトリ構成を抑えておく。クリーンアーキテクチャに沿ったディレクトリ構成に加えて、今回templateを作成するために必要なファイル群はtemplatesディレクトリとして分けた。
ここにコマンド実行のためのmain.goを配置する形にしている。
まずテンプレートとなるファイルを用意する。
今回は比較的記述の少ないrepository層の記述を自動生成してみる。
先ほどとは異なり、埋め込み部分は{{.UpperCamelCalse}}
となっていて、これは変数として埋め込みができるようにこうしている。
ファイルによって埋め込む文字列がUpperCamelCaseとlowerCamelCaseの両方だったりすることと、可読性向上のためにこうしている。
次にこのtemplateファイルを使用してファイルを自動生成する部分の記述。
11-14行目で受け取った引数のsnake_caseをlowerCamelCaseとUpperCamelCaseに変換している。
16行目で先ほどのテンプレートファイルを使用して、*template.Template
を生成。
20行目でusecase/repository
配下に必要なファイルを作成して、26行目でupperCamelのmapでテンプレートに埋め込む文字を渡している。
これで以下のようにコマンドを叩いてみる。
すると該当のファイルが生成される。
あとは他のファイルも自動的に生成されるようにそれぞれのtemplateファイルを用意した上で、自動生成するコード部分を以下のようにループ処理させるように変更する。
これでコマンドを叩くだけでボイラープレートが一括生成できるようになった。
あとは目的の処理に応じて記述内容を修正していけばよい。
ついでにmakefileを使ってコマンドを管理した。
このコマンド1つで5つのファイルをそれっぽい記述が書かれた状態で自動生成できるようになったので開発効率がかなり上がってめでたし。
Goって本当こういう標準ライブラリ充実してるなと思う
引数がインターフェースになってて汎用性ある形になってるし、色々応用効きそうなのもよい
こういう自動化作業はやってて非常に楽しいし達成感が得られやすいから好きだ
https://pkg.go.dev/text/template
Go text/template で文字列作成 - QiitaGo text/template で文字列作成みなさん、こんにちは!Go書いてますか?今回は、text/template を使って ひな形 から 文字列 を生成する方法をご紹介します。tex…qiita.com