Golangを使い始めてinterfaceでDIPっぽいことをしようとするとたしかに湧きがちな疑問のひとつ。結論から言うと、interfaceはそれを使う側のパッケージに所属させるのがセオリーらしい。なるほど。
Go interfaces generally belong in the package that uses values of the interface type, not the package that implements those values.
CodeReviewComments · golang/go Wiki · GitHub
なぜか
せっかくなので理由を考えてみた。
以下はJSONやXMLなど複数のフォーマットでログを出力できるロガー・アプリケーションのクラス関係図の例。
このアプリケーションの設計は、中核となるLogger
クラスと具体的なXMLやJSONなどへのシリアライザ実装がそれぞれISerializer
インターフェースという抽象に依存している典型的なDIP(依存性逆転)だ。LogParams
クラスは、ログとして出力するデータの内容を保持しているものとする。
それぞれ、黒矢印は「利用」、白矢印は抽象に対する「実装」、点線の矢印は「依存」を表している。
まず、このアプリケーションにおいてserializer
パッケージというのは「どのように出力するか?」のHowが所属するレイヤであり、一方でmain
パッケージはアプリケーションのビジネスロジックとして「なにをするのか?」のWhatが所属するレイヤにあたる。
そう考えると、具体的な実装を持たないinterfaceであるISerializer
が、「出力の方法」を責務として所属させるべきserializer
パッケージにいるというのは違和感がある。なぜならinterfaceは単なる「使い方」を示すだけのものであって具体的な実装を持つものではないのに、Howが所属するパッケージにいるからだ。このような理由で、interfaceはそれを使う側に配置するのがよい、というプラクティスに合点がいく。
(ちなみにではあるが、そもそもこの関係図だとmainパッケージとserializerパッケージが双方向依存になっているため、Golangではコンパイルの時点でエラーになってしまう)
ということでISerializer
をmain
パッケージに所属させる。
パッケージ内での依存関係はいくら循環していようと構わないので、これは問題なく動作する。また、ISerializer
インターフェースが実装先のパッケージから切り離された。これで正しく「使う側」に所属させることができた。
DDD的な観点: 例えば今回のISerializer
に当たる部分が、DDDでいうところのリポジトリであると考えてみる。そう考えると、リポジトリはドメイン層のインターフェースに当たる部分なので、リポジトリの具体的な実装のパッケージにそれが所属するのは解せない、というのがしっくりくる。意図せずここで不思議なつながりが見えた気がする(?)

Clean Architecture 達人に学ぶソフトウェアの構造と設計
- 作者: Robert C.Martin,角征典,高木正弘
- 出版社/メーカー: KADOKAWA
- 発売日: 2018/07/27
- メディア: 単行本
- この商品を含むブログを見る