goaを使ったサービス開発では、まずAPIやresourceの定義をdesignと呼ばれるコードに記述し → それをinputとしてgeneratorで実稼働コードの雛形を生成 するという開発手順を踏む事になるが、新規でdesignを書くだけではなく既存designの修正時にも同様に再生成作業が発生する事がある。goaが提供するコードジェネレータ goagen
には --regen
という再生成の為に用意されたと思われるオプションが提供されているが、どういう挙動をするか調査したのでメモ
前提条件
- goa ver 1.3.0
- 対象controller actionはGET(参照系処理)
- controllerのコードに既に非・自動生成なコード(主にビジネスロジック)も混ざっているケースで、controller のdesignに変更が発生 => その変更差分をgoagenを再実行してcontrollerコードに摘要したい。というケース
goaで提供されている goagen --regen
と goagen --force
goagen
再生成を意識して用意されたと思われるオプションは --regen
と --force
の2つある。例えば goagen main ...
コマンドでcontrollerを生成する際にこの2つのオプションがどういう挙動をするかは goagen/gen_main/generator.goの GenerateControllerメソッド のコードを読むと分かる
if regen {
actionImpls, extractedImports, err = extractControllerBody(filename)
if err != nil {
return "", err
}
os.Remove(filename)
}
if force {
os.Remove(filename)
}
-
--force
の場合はファイルそのものが自動生成ファイルで上書きされる。なので独自処理コードを追加していた場合はそのコードはなくなる -
--regen
の場合は、goagen直後のコードに含まれるstart_implement
というコメントとend_implement
というコメントの存在を探し、それらの間に独自追加されたコードを維持しつつ、それ以外の部分が上書き される- つまり独自処理は、この両コメントを残しつつ、両コメントの間に書くと良さそう
--regen
の際、独自追加コード or NOT を判定する為の解析処理が https://github.com/goadesign/goa/blob/master/goagen/gen_main/generator.go#L74で行われている。
- 現存しているcontrollerコードの
// XXXController_ACTIONNAME: start_implement
というコメントと、// XXXController_ACTIONNAME: end_implement
というコメントで挟まれているコードブロックを探す - このブロック(の各行)を
actionImpls[match[1]] = strings.Join(block, "\n")
という変数に退避。最終的にこのactionImpls
をreturnする - このブロック以外(start ... end の外)は、genされたコードで上書きする
という挙動。
この際に end_implement
コメントの直後に res := &app.XXXList{}
的な空struct代入コードも復活するのだけど、例えば end_implement
コメントより前の独自処理記載部分に res := 処理結果
という代入を行うコードがある場合だと、この空struct代入コードにより結果が res
変数が上書きされる事になる。
// HogeController_SearchHoge: start_implement
// Put your logic here
res := 実際の処理結果 // 再生成でも維持
// HogeController_SearchHoge: end_implement
res := &app.HogeList{} // ★再生成で空struct上書きコード復活
return ctx.OK(res)
これを回避する為、最初「実際の処理結果は res
という変数以外の変数を使うようにする」という策を思い付いたのだが、controllerの自動生成では例えばGETの正常系なら return ctx.OK(res)
という結果返却コードを生成してくれるので、このreturnで使っている変数も res
以外にrenameする という修正が発生する事になる。つまり自動生成部分に手を加える という事になり、保守性の観点で懸念があるものの、 "--regen way" に乗るなら再生成はこの流れで行う事になりそう。
「空struct代入コードは end_implement
より前の行に生成されるようなpull reqを出す」という案も考えたのだけど、空struct代入を end_implement
の後ろにしている意図を完全に理解し切れていなかった為、一旦却下
補足: gormaでmodelを管理している場合
gormaはgoaのプラグインとして公開されているgormベースのO/Rマッパーで、このプラグインを導入する事で「modelを独自DSLでdesignとして定義」する事ができるようになり、このdesignをinputとして goagen gen
コマンドを実行する事で、gormベースのmodelのコードが自動生成される。デフォルトでは、
-
モデル名.go
というコードを生成: 基本的なCRUD処理はココに自動定義される -
モデル名_helper.go
というコードを生成: "controllerで使用可能なレスポンス用型(media_type)への変換を含んだ参照系" 処理はココに自動定義される
という挙動になっており、独自コードは単にファイルを分けて定義するようにすれば、再生成・上書き の考慮は不要になる。例えばgoa公式のサンプルでは独自処理は モデル名_addon.go
というファイルに分けている。ただこの場合も、例えば「modelの項目構成が変わった」り、「media_typeの内部定義が変わった」りした場合に、その変更を考慮した修正が独自処理にも発生する事はある。