Table of Contents
「Goslings開発メモ - その1: Spring Boot編」の続き。
Spring Boot続編で、DIについて。
DIとは
DIはDependency Injectionの略。依存性注入と訳される。
これは、Javaの文脈で具体的目に言うと、あるクラスが依存する具象クラスのインスタンス化と取得をフレームワークに任せることで、具象クラス間の直接的な依存を排除し、よってコンポーネント間を疎結合にする手法。 これにより、アプリの拡張性を高めたり、テストがしやすくなったりする。(参考記事)
Spring FrameworkはもともとこのDI機能を提供するフレームワーク(i.e. DIコンテナ)として普及した。
GoslingsでDI
Goslingsサーバの内部機能はざっくり、クライアントからのREST API呼び出しを処理するユーザインタフェース層と、Gitリポジトリにアクセスするデータベース層に分かれる。
Gitリポジトリにアクセスする部分は今回はJGitで実装するが、将来的に別のライブラリで実装しなおす可能性が微レ存なのと、Goslingsの開発自体がWebアプリ開発の練習でもあるので、ちゃんとしたアーキテクチャでと思い、DAOパターンを使ってやった。
つまり例えば、GitのコミットオブジェクトはJGitのAPIではRevCommitクラス
で表されるが、ユーザインタフェース層からはリソースクラスであるCommitクラス(前回参照)を扱う以下の様なDAOインターフェースを呼ぶようにし、JGit依存の実装とは切り離す。
public interface ObjectDao {
public Commit[] getCommits(String token) throws DaoException;
}
(ObjectDao.javaの完全なソースはこれ)
ObjectDao
を実装するObjectDaoImpl
クラスでは、以下の様にJGitを使ってごりごりと実装を書く。
public final class ObjectDaoImpl implements ObjectDao {
// フィールド定義は省略
@Override
public Commit[] getCommits(String token) {
try {
return StreamSupport.stream(resolver.getGit(token).log().all().call().spliterator(), false)
.map(this::convertToCommit)
.toArray(Commit[]::new);
} catch (NoHeadException e) {
// エラー処理
}
}
private Commit convertToCommit(RevCommit commit) {
// RevCommitをCommitに変換する処理
}
}
ユーザインターフェース層はRestApiV1Controller
クラス(前回参照)のgetCommits
メソッドで、以下の様にObjectDaoを使いたい。
public final class RestApiV1Controller {
private ObjectDao objectDao;
@RequestMapping(path="{token}/objects/commits")
public Commit[] getCommits(@PathVariable String token) {
return objectDao.getCommits(token);
}
// 以下他のメソッド
}
ここで問題になるのが、RestApiV1Controller
のobjectDao
フィールドへのインスタンスの代入だが、RestApiV1Controller
内(e.g. RestApiV1Controller
のコンストラクタ)でObjectDaoImpl
をインスタンス化して代入するのでは、ObjectDaoImpl
というデータベース層の具象クラスへの直接的な依存(i.e. import ObjectDaoImpl
)が発生してしまってまずい。
ユーザインターフェース層とデータベース層が密に結合してしまう。
ここがDIの使いどころだ。
RestApiV1Controller
へのObjectDaoImpl
インスタンスの注入をフレームワークに任せればいい。
Spring BootでのDI
Spring BootアプリではSpring FrameworkのDI機能を何でも使えるが、普通、もっとも簡単な方法である@ComponentScan
と@Autowired
を使う方法を採る。
まずは@ComponentScan
だが、これは、前回書いたように既に使っていて、プロジェクト内の全てのSpring Beanが検索されDIコンテナに登録されるようになっている。
なので、注入したいObjectDaoImpl
がSpring Beanと判定されるようにすればよい。
そのためには、ObjectDaoImpl
に以下のアノテーションのいずれかを付ける必要がある。
@Service
: 業務手続を表すAPIを提供する(しばしば状態を持たない)コンポーネント。またはそれっぽいもの。MVCアーキテクチャのM(モデル)や、3層アーキテクチャのビジネスロジック層のコンポーネント。@Repository
: データの保持、取得、検索といった振る舞いを持つ、オブジェクトコレクションを表すコンポーネント。またはそれっぽいもの。MVCアーキテクチャのM(モデル)の内、特にデータベースを扱うコンポーネントや、3層アーキテクチャのデータベース層のコンポーネント。@Controller
: MVCアーキテクチャのC(コントローラ)のコンポーネント。@Component
: 一般的なコンポーネント。
(参考記事)
ObjectDaoImpl
はDAOコンポーネントで、これはもちろん@Repository
にあたるのでこれを付ける。
@Repository
public final class ObjectDaoImpl implements ObjectDao {
// 省略
}
これでObjectDaoImpl
がSpring Beanとして登録されるので、あとはRestApiV1Controller
に@Autowired
で注入してやればいい。
public final class RestApiV1Controller {
@Autowired
private ObjectDao objectDao;
// 以下省略。
}
@Autowired
を付けたことにより、RestApiV1Controller
のインスタンス化直後に、objectDao
フィールドに適切なSpring Beanが注入されるようになった。
注入されるSpring Beanはフィールドの型から判断される。
objectDao
フィールドの型はObjectDao
で、この実装はプロジェクト内にObjectDaoImpl
しかないので、狙い通りObjectDaoImpl
が注入される。
今はこれでもいいが、将来ObjectDao
の実装が増えた場合、どの実装を注入すべきかSpring Frameworkには分からなくなるので、今のうちに@Qualifier
を使って明示しておくことにする。(参考)
まずSpring Beanの方にjgit
という値を持つ@Qualifier
をつける。
@Repository
@Qualifier("jgit")
public final class ObjectDaoImpl implements ObjectDao {
// 省略
}
(ObjectDaoImpl.javaの完全なソースはこれ)
Spring Beanを使う側にも同じ@Qualifier
をつける。
public final class RestApiV1Controller {
@Autowired
@Qualifier("jgit")
private ObjectDao objectDao;
// 以下省略。
}
(RestApiV1Controller.javaの完全なソースはこちら)
これでRestApiV1Controller
のobjectDao
フィールドにどのObjectDao
実装が注入されるかがより明確になった。
将来ObjectDao
の別の実装を作るときには、その実装クラスには別の値の@Qualifier
を付けてやれば、RestApiV1Controller
の方の@Qualifier
の値によって注入する実装を切り替えられる。
今日はここまで。 次回もまたSpring Bootで、例外処理について。