なぜデータの保存先を Usecase 層に意識させたくないのか
(下記は こちら で紹介されているような知識を前提にしています)
永続化の責務を誰が持つべきか?
アプリケーションで「データを永続化したい」という要件があるとき、Usecase 層はデータを永続化するためのメソッドを呼び出しはするが、「どうやって」永続化するか?は Usecase 層は知るべきでない。
あくまで Gateway 以下にその詳細は書かれる。
という前提知識がある中で、筆者は以下のように思った:
「外部 API を叩いて得たデータをファイルに保存する」というだけの小さなアプリを作りたい。
ファイルに保存することがこのアプリの作成の目的であるため、
「どうやって保存する」という情報が Usecase 層に書かれないといけないのでは?
つまり下記のようなコードを書きたくなった。(コード例は Kotlin)
class ExportXxxUsecase(
xxxPort: XxxPort
) {
fun execute(id: XxxId) {
val xxx = xxxPort.find(id)
xxxPort.save(xxx, "/tmp/output.csv")
}
}
永続化の詳細を隠すべき理由
でもちょっと待ってほしい。
ファイルパスが Usecase 層にあらわれている時点で、永続化にファイルを使うことを Usecase 層が知ってしまっているので、冒頭に書いた前提と矛盾している。
また、例えばファイルでなくデータベースに永続化しようという切り替えを行う際はどう考えても save メソッドの第2引数が邪魔になるので、Port のインタフェースから変更しなければならないというのも変である。
つまり、
「どうやって保存する」という情報が Usecase 層に書かれないといけないのでは?
というのは誤りだと考えられる。
つまり以下のようなコードを目指すべきである。
class ExportXxxUsecase(
xxxPort: XxxPort
) {
fun execute(id: XxxId) {
val xxx = xxxPort.find(id)
xxxPort.save(xxx)
}
}
class XxxFileGateway(private val outputDir: Path) {
fun save(xxx: Xxx) {
// some implementation
}
}
class XxxDbGateway(private val connection: Connection) {
fun save(xxx: Xxx) {
// some implementation
}
}
永続化の意識を分離するための考察
では最初に考えていた、
ファイルに保存することがこのアプリの作成の目的であるため、
「どうやって保存する」という情報が Usecase 層に書かれないといけないのでは?
これを否定しきるための理由付けを考えたい。
ということで筆者なりの結論は、
- Usecase 層は フィルタリングや変形などの計算処理を書く 場所である
- 入力や出力の具体というのは変形などの処理ではない
- そもそもファイルの保存先を気にしたくなったのはなぜ?
- ファイルに保存することがこのアプリの作成の目的であるため
- 「アプリが達成したいこと」と「Usecase 層が担う責務」はそもそも別であるため、Usecase に出力先を書く理由にはならない
- そのファイルを使って次の処理をするときに、出力先がわかってないと次の処理をはじめられないから
- では極論、次の処理とやらが DB に JSON でデータ保存されてることを期待するのならそうするのか?
- ということで「次の処理のため」をわずかでも気にするのは誤り
- ファイルに保存することがこのアプリの作成の目的であるため
「あー、もしかして Usecase って、そのアプリでどういう計算処理をしたいかだけ書く場所なんだろうなー」となんとなく気づいてから納得までが早かったので思考の発散は大事。
Discussion