Go言語でのimport cycle not allowedに対処する
はじめに
golang でのWeb開発では、RubyにおけるRailsのようなデファクトスタンダードなフレームワークがないので、 パッケージ構成に関して、開発者に大きな裁量があります。
通常、Web開発では多くのフレームワークが採用しているMVC + Service的な形のパッケージ構成にすることが 無難ではないかと思います。
しかし、標準パッケージやOSSのコードでは結構フリーダムな構成(に見える)が多く、 一時期私も「こういう感じがGoっぽいぜ!」と若干厨二的な発想に陥り、 あえてMVC + Service的な構成を取らずに開発をすることがありました。
結果、表題の import cycle not allowed
に悩まされることが増え、
最近またMVC + Serviceの形に戻すようになりました。
(この戻す作業が本当に大変でした...)
パッケージ構成についてはそれはそれで面白いテーマですが、 今回はどうしても避けられないimport cycleが発生した場合、どう対処するか?というお話です。
なぜimport cycleが禁止されているのか?
公式なドキュメントが見つけられなかったのですが、コンパイラ(Go Compiler)の実装をシンプルにするためでしょう。
Go Compilerは、複雑な実装を避けるためCやC++のような高度なオプティマイザも無ければ、 インライン関数もサポートしていません。 同じ理由でこのimport cycleもサポートしていないのだと思います。
Javaなんかは意外とOKなんですね。 昔苦しんだ記憶があるのですが、相互参照による予期せぬ動作、とかだったのかもしれません。
個人的には、やはりGoやC言語のようにコンパイルでエラーにするのが良いと思います。
対処方法
intarfaceを用いるのが一般的なようです。
Before
package foo import ( "fmt" "bar" ) type Foo struct{} func (f Foo) P() { fmt.Println("foo") } func CallBar() { bar.Bar{}.P() }
package bar import ( "fmt" "foo" ) type Bar struct{} func (b Bar) P() { fmt.Println("bar") } func CallFoo() { foo.Foo{}.P() }
import cycle not allowed package foo imports bar imports foo
After
package foo import ( "buz" "fmt" ) type Foo struct{} func (f Foo) P() { fmt.Println("foo") } func CallBar(p buz.Printer) { p.P() }
package bar import ( "buz" "fmt" ) type Bar struct{} func (b Bar) P() { fmt.Println("bar") } func CallFoo(p buz.Printer) { p.P() }
package buz type Printer interface { P() }
package hoge import ( "bar" "foo" ) func Call() { foo.CallBar(bar.Bar{}) bar.CallFoo(foo.Foo{}) }
依存関係は以下のようになります
foo => buz bar => buz hoge => foo, bar
最後に
一通り苦しんだ後標準パッケージの構成を改めてみると、 相当な検討、議論があって今の形になっているということがわかった気がします。 外面だけ真似ても駄目ですね(^^;)