令和版: 依存関係逆転の法則の実現方法

# 依存関係逆転の法則とは

コアのロジックが実装の詳細に依存しないようにして、モジュール間を疎結合にしましょうという原則。
Wikipediaでは以下のように説明されている。

> 1. 上位モジュールはいかなるものも下位モジュールから持ち込んではならない。双方とも抽象（例としてインターフェース）に依存するべきである。
> "High-level modules should not import anything from low-level modules. Both should depend on abstractions (e.g., interfaces)."
> 2. 抽象は詳細に依存してはならない。詳細（具象的な実装内容）が抽象に依存するべきである。
> "Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions."


# ダメな例

非常に簡単な例を挙げよう。
ユーザIDからユーザ名を引くだけの非常に簡単なサービスを考える。
これを単純に実装すると、以下のようなプログラムになるだろう。(筆者の好みでScalaで解説する)

```scala
object FetchUserService {
  def run(userId: Int): Future[Either[ServiceError, String]] = {
    UserRepository.select(userId).map {
      case Some(user) => Right(user.name)
      case None       => Left(ServiceError.notFound)
    }
  }
}
object UserRepository {
  def select(id: Int): Future[Option[User]] = {
    // DBアクセスなどの下位ロジック
  }
}
```

この実装では `FetchUserService` が `UserRepository.select` を直接使っているため、 サービスのコア部分のロジックがDBアクセスのような下位の実装に依存してしまっている。
それの何がまずいかというと `FetchUserService` をテストしようとすると、必然的に DB を用意する必要が出てくるという点である。

サービスのコア部分は重点的にテストをするべき場所であるため、テストが簡単にできる状態にしておきたい。
そのため、下位モジュールへの依存をできるだけ断ち切り、単体でテスト可能な設計にしておくことが重要なのである。


# よくある解説

依存関係逆転の法則のよくある解説では、この問題をインターフェースを用いて解決している。Scala ならば `trait` を使うことになる。

例えば、以下のようにする。

```scala
class FetchUserService @Inject()(userRepository: UserRepository) {
  def run(userId: Int): Future[Either[ServiceError, String]] = {
    userRepository.select(userId).map {
      case Some(user) => Right(user.name)
      case None       => Left(ServiceError.notFound)
    }
  }
}
trait UserRepository {
  def select(id: Int): Future[Option[User]]
}
object UserRepositoryImpl extends UserRepository {
  def select(id: Int): Future[Option[User]] = {
    // DBアクセスなどの下位ロジック
  }
}
```

`UserRepository` を `trait` 化し、DBアクセスなどの下位ロジックはそれを実装する `UserRepositoryImpl` 側におく。
`FetchUserService` は `UserRepository` を実装したインスタンスを DI などを経由して受け取り、それを利用して `select` するようにする。

こうすることで、`FetchUserService` はDBアクセスロジックに直接依存しなくなった。
テスト時にはモック版の `UserRepository` を渡すことで DB がなくてもテストが可能である。

`UserRepository` は下位モジュールが満たすべき仕様を定めたものなので、これも上位のモジュールであるとみなせば、上位モジュールである `FetchUserService` と `UserRepository` は他のモジュールに依存しておらず、下位モジュール `UserRepositoryImpl` は上位モジュールに依存していることになる。
最初の例の時と依存関係が逆転しているため、依存関係逆転の法則と呼ばれる。


# この方法の問題点

この方法にはいくつかの問題点がある。

まず一つは、`FetchUserService` の単体テストのために `UserRepository` のモックが必要となることだ。
モックを利用したテストはしばしばモック側のバグにより壊れることがあり、モックのメンテナンスに馬鹿にならないコストがかかる。
サービスの単体テストのはずがモックのテストになっていたなんて話もしばしば耳にする。

次の問題点は、DB側の都合が `select` の返り値の型として漏れ出していることである。
`select` の返り値の型が `Future` であるのは完全に DB 側の都合であり、`FetchUserService` からすれば関心がないはずだ。
実際、`FetchUserService` では `Future` が関わるような処理は `select` 以外していない。
`Future` が絡んでこないのであれば、`FetchUserService` は単なる逐次実行プログラムとなり、よりテストしやすくなるはずである。


# ではどうするか

ここで依存関係逆転の法則を思い出そう。

> 上位モジュールはいかなるものも下位モジュールから持ち込んではならない。双方とも抽象（例としてインターフェース）に依存するべきである。

**例として**インターフェースに依存するべきと書いてある。
インターフェースを使うというのは実装方法の一つに過ぎないのである。

> 抽象は詳細に依存してはならない。詳細（具象的な実装内容）が抽象に依存するべきである。

つまり、具象的な実装内容に依存しない形で「抽象」を定義し、それに依存するようにすれば良いのである。


# リクエストオブジェクトを介した依存

そこで、本稿では `select` を実際に実行する代わりに、`UserRepository` に実行してほしい内容を記述した**リクエストオブジェクト**を作ることを考える。
インターフェースの代わりにこのリクエストオブジェクトを「抽象」として利用する。
これは例えば以下のようなデータ型である。

``` scala
case class SelectUserRequest(id: Int)
```

このデータを `UserRepository` に渡すと、`UserRepository` はそれに従って処理を実行する。

```scala
object UserRepository {
  def dispatch(request: SelectUserRequest): Future[Option[User]] = {
    // DBアクセスなどの下位ロジック
  }
}
```

さて、このオブジェクトを `FetchUserService` が `UserRepository` に直接渡すのでは意味がない。
`FetchUserService` が行うべきは、 `SelectUserRequest` とそれが返してきた結果をその後どう処理するか(**継続**と呼ぶ)のペアを作って返すことである。
そうすれば、あとは `FetchUserService` の外側で処理を実行することができる。

```scala
object FetchUserService {
  def run(userId: Int): (SelectUserRequest, Option[User] => Either[ServiceError, String]) = {
    // リクエスト
    val request = SelectUserRequest(userId)

    // 返ってきたものをどう処理するか (継続)
    val cont: Option[User] => Either[ServiceError, String] = (userOpt: Option[User]) => {
      case Some(user) => Right(user.name)
      case None       => Left(ServiceError.notFound)
    }

    // ペアにして返す
    (request, cont)
  }
}
```

全体の処理を実行するには、`FetchUserService.run` を実行した後で、返されたリクエストを `UserRepository` に渡し、その結果をさらに継続に渡せば良い。

```scala
// アプリケーションとして公開する関数
def fetchUser(userId: Int): Future[Either[ServiceError, String]] = {
  val (request, cont) = FetchUserService.run(userId)
  val responseFuture = UserRepository.dispatch(request)
  responseFuture.map(response => cont(response))
}
```

これで `FetchUserService` から完全に `UserRepository` への依存を排除することができた。
また `Future` も `FetchUserService` からなくなっている。

`FetchUserService` の返り値は一見複雑になっているが、一つ目の返り値の `SelectUserRequest` は単なるデータ型であるため容易にテストできる。
二つ目の返り値である継続は関数オブジェクトなので、通常の単体テストと同じく入力を与えて出力を検査すれば良い。


# 一般化する

この方法を一般化していこう。

まずは、リクエストを一般化する。

`SelectUserRequest` 以外のリクエストを可能にするために、一般的なリクエストの型 `RepositoryRequest` を定義する。
`RepositoryRequest` は返却するべきオブジェクトの型情報を持つ必要がある。
また、リポジトリごとにリクエストを分岐させるために、リポジトリ単位のリクエスト型も用意しておこう。

``` scala
sealed trait RepositoryRequest[T]
sealed trait UserRepositoryRequest[T] extends RepositoryRequest[T]
case class SelectUserRequest(id: Int) extends UserRepositoryRequest[Option[User]]
```

これを処理するリポジトリは次のようになるだろう。

```scala
object Repository {
  def dispatch[T](request: RepositoryRequest[T]): Future[T] = request match {
    case userRepositoryRequest: UserRepositoryRequest[T] => UserRepository.dispatch(userRepositoryRequest)
  }
}
object UserRepository {
  def dispatch[T](request: UserRepositoryRequest[T]): Future[T] = request match {
    case SelectUserRequest(id) => select(id)   // Scala2 の場合は asInstanceOf[Future[T]] が必要なようだ
  }
  private def select(id: Int): Future[Option[User]] = {
    // DBアクセスなどの下位ロジック
  }
}
```


次に、`FetchUserService` が返すべき型を一般化する。
サービスが返すべき型は、まだリポジトリに対するリクエストがあるなら、リクエストと継続のペアになるはずである。
ただし、その継続の部分でもリポジトリに対してリクエストを行う可能性がある点は留意しておく必要がある。
もしリポジトリに対するリクエストがないのであれば、サービス自体の返り値を返すはずである。

つまり、以下のような型を考えれば良い。

```scala
sealed trait ServiceLogic[R]
case class Continue[T, R](request: RepositoryRequest[T], cont: T => ServiceLogic[R]) extends ServiceLogic[R]
case class Done[R](value: R) extends ServiceLogic[R]
```

このとき `FetchUserService` は以下のようになる。

```scala
object FetchUserService {
  def run(userId: Int): ServiceLogic[Either[ServiceError, String]] = {
    val request = SelectUserRequest(userId)
    val cont: Option[User] => ServiceLogic[Either[ServiceError, String]] = {
      (userOpt: Option[User]) => {
        case Some(user) => Done(Right(user.name))
        case None       => Done(Left(ServiceError.notFound))
      }
    }
    Continue(request, cont)
  }
}
```

任意の `ServiceLogic` を実行するためのヘルパー関数を定義しておこう。

```scala
def runServiceLogic[R](serviceLogic: ServiceLogic[R]): Future[R] = serviceLogic match {
  case continue: Continue[_, R] => runContinue(continue)
  case Done(value)              => Future(value)
}
private def runContinue[T, R](continue: Continue[T, R]): Future[R] = {
  Repository.dispatch(continue.request).flatMap { t =>
    runServiceLogic(continue.cont(t))
  }
}
```

すると、`fetchUser` は以下のようになる。

```scala
// アプリケーションとして公開する関数
def fetchUser(userId: Int): Future[Either[ServiceError, String]] = {
  runServiceLogic(FetchUserService.run(userId))
}
```

以上で完成である。
上位のモジュールである `FetchUserService` は下位モジュールである `UserRepository` にまったく依存しておらず、容易に単体テストが可能になっていることがわかる。

# まとめ

* インターフェースの利用は依存関係逆転の法則の実現方法の一つでしかなく、またその方法にはいくつか問題があった
* 依存関係逆転の法則の「抽象」としてリクエストオブジェクトを利用することで、これらの問題を解決できることを示した


# 追記: より便利にするには

`ServiceLogic` の `Continue` は `T => ServiceLogic[R]` という型の引数をとる。
この型はどこかでみたことがないだろうか。そう、`flatMap` の引数と同じ型である。

`Continue` が第一引数として `RepositoryRequest` ではなく `ServiceLogic` を取るようにすれば、 `Continue` の生成は `flatMap` と等価ということになる。


```scala
sealed trait ServiceLogic[R] {
  def flatMap[U](f: R => ServiceLogic[U]): ServiceLogic[U] = Continue(this, f)
}
case class Continue[T, R](serviceLogic: ServiceLogic[T], cont: T => ServiceLogic[R]) extends ServiceLogic[R]
```

また、`ServiceLogic` に `map` も実装しておくと便利だろう。

```scala
sealed trait ServiceLogic[R] {
  def map[U](f: R => U): ServiceLogic[U] = Continue(this, r => Done(f(r)))
}
```

`Continue` が`RepositoryRequest` ではなく `ServiceLogic` を取ると `RepositoryRequest` から `ServiceLogic` を生成する方法がなくなってしまうので、代わりに `RepositoryRequest` のレスポンスを直接返り値とするような `ServiceLogic` を生成できるようにしておこう。

```scala
case class Suspend[T](request: RepositoryRequest[T]) extends ServiceLogic[T]
```

`ServiceLogic` の定義はこうなる。

```scala
sealed trait ServiceLogic[R] {
  def flatMap[U](f: R => ServiceLogic[U]): ServiceLogic[U] = Continue(this, f)
  def map[U](f: R => U): ServiceLogic[U] = Continue(this, r => Done(f(r)))
}
case class Suspend[T](request: RepositoryRequest[T]) extends ServiceLogic[T]
case class Continue[T, R](serviceLogic: ServiceLogic[T], cont: T => ServiceLogic[R]) extends ServiceLogic[R]
case class Done[R](value: R) extends ServiceLogic[R]
```

こうしておくと、`FetchUserService` は以下のように書ける。

```scala
object FetchUserService {
  def run(userId: Int): ServiceLogic[Either[ServiceError, String]] = {
    Suspend(SelectUserRequest(userId)).map {
      case Some(user) => Right(user.name)
      case None       => Left(ServiceError.notFound)
    }
  }
}
```

例えば、ここで直接 `Suspend[Option[User]]` を生成するようなビルダーとして `UserRepositoryLogic.select` を定義してみよう。

```scala
object UserRepositoryLogic {
  def select(userId: Int): ServiceLogic[Option[User]] = {
    Suspend(SelectUserRequest(userId))
  }
}
```

すると `FetchUserService` はこうなる。

```scala
object FetchUserService {
  def run(userId: Int): ServiceLogic[Either[ServiceError, String]] = {
    UserRepositoryLogic.select(userId).map {
      case Some(user) => Right(user.name)
      case None       => Left(ServiceError.notFound)
    }
  }
}
```

さて、最初のコードを思い出そう。
最初のコードでは `FetchUserService` は以下のように書いた。

```scala
object FetchUserService {
  def run(userId: Int): Future[Either[ServiceError, String]] = {
    UserRepository.select(userId).map {
      case Some(user) => Right(user.name)
      case None       => Left(ServiceError.notFound)
    }
  }
}
```

見てわかる通り、ほぼ同じコードとなっている。


# 追記の追記: Free

`ServiceLogic` の定義は最終的にこうなった。

```scala
sealed trait ServiceLogic[R] {
  def flatMap[U](f: R => ServiceLogic[U]): ServiceLogic[U] = Continue(this, f)
  def map[U](f: R => U): ServiceLogic[U] = Continue(this, r => Done(f(r)))
}
case class Suspend[T](request: RepositoryRequest[T]) extends ServiceLogic[T]
case class Continue[T, R](serviceLogic: ServiceLogic[T], cont: T => ServiceLogic[R]) extends ServiceLogic[R]
case class Done[R](value: R) extends ServiceLogic[R]
```

実はこの型は `Free` という名前でよく知られたものである。
ただし、`Free` では `RepositoryRequest` の部分が型引数として与えられる。

以下は cats の `Free` の定義 (抜粋 & 一部改変) である。

```scala
sealed trait Free[S[_], R] {
  def flatMap[U](f: R => Free[S, U]): Free[S, U] = FlatMapped(this, f)
  def map[U](f: R => U): Free[S, U] = FlatMapped(this, r => Pure(f(r)))
}
case class Suspend[S[_], T](request: S[T]) extends Free[S, T]
case class FlatMapped[S[_], T, R](free: Free[S, T], cont: T => Free[S, R]) extends Free[S, R]
case class Pure[S[_], R](value: R) extends Free[S, R]
```

つまり cats を利用している場合、`ServiceLogic` は実は自分で定義する必要はない。
`RepositoryRequest` を定義すれば、あとは `Free` に与えるだけで `ServiceLogic` を実装できる。

```scala
type ServiceLogic[R] = Free[RepositoryRequest, R]
```

実行するときは `RepositoryRequest` を計算する方法を与えるだけで良い。

```scala
// FunctionK (自然変換) の定義方法
val compiler = new (RepositoryRequest ~> Future) {
  def apply[A](req: RepositoryRequest[A]): Future[A] = Repository.dispatch(req)
}
def runServiceLogic[R](serviceLogic: ServiceLogic[R]): Future[R] = {
  serviceLogic.foldMap(compiler)
}
```