Scala で書く関数型な DI： Cake 〜 Free+ReaderT、ZIO

["Functional Programming Patterns in Scala and Clojure"](https://www.amazon.co.jp/Functional-Programming-Patterns-Scala-Clojure/dp/1937785475)(以下、FPPSC)([PDF](https://doc.lagout.org/programmation/Functional%20Programming%20Patterns%20in%20Scala%20and%20Clojure_%20Write%20Lean%20Programs%20for%20the%20JVM%20%5BBevilacqua-Linn%202013-11-02%5D.pdf))の第3章に、関数型言語のイディオムでオブジェクト指向のパターンを置き換える「パターン」集が載っていて、その中で OO 的な DI の代替技法として **Cake パターン**が紹介されている。

11個紹介されている「置き換えパターン」の中でも、この Cake は特に、OOP vs FPというより、Scalaならこういう書き方もできるという言語特有イディオムの紹介だけど、これをもう少し関数型っぽく発展させてみたい。以下 Cake Pattern、Minimal Cake Pattern、Reader、Free、Reader + Free、ZIO を順に試してみる。

※『[DI と後出しモナド：Tagless Final / Free Monad](https://qiita.com/yasuabe2613/items/5b816a125f073ff01932)』に続く

## 準備等
使ったライブラリ等は以下。

- Scala: 3.1.3
- cats: 2.8.0
- cats effect: 3.3.12
- ZIO: 2.0.0

[書籍のサンプルコード](https://github.com/bf4/book_code/blob/master/mbfpp/code/ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/di/ex1/Services.scala)を出発点にする。ただし後の説明のため以下の点を少し改変する。

- エンティティと DAO のペアが `Movie`用と`Video`用の二組あるが、これを`Movie`用の1個にした。
- 「Dao」から返される`Movie`型を`IO[Option[_]]`に入れた。
- Dao → Repo、ServiceComponentImpl → ServiceComponentなど若干ネーミングを変更した。
- テストコードなどは適当に変更した。

`Movie`エンティティは以下のような 'case class'で定義し、RDBを模した`Map`にサンプルデータを一つ入れておく。

```scala
case class Movie(id: Int, title: String)

val db = Map[Int, Movie](42 -> Movie(42, "A Movie"))
```
## パターンいろいろ
### Cake Pattern
有名なオリジナルの Cake パターンで、FPPSCではこれが紹介されている。Java 由来のDIフレームワークより特に優れているというものでもないが、実行時DIとコンパイル時DIという点で本質的に違う。


以下がオリジナルの Cake 特有の構成で、各`trait`がパラレルな入れ子になっている。

```scala
trait MovieRepoComponent:
  trait MovieRepo:
    def getMovie(id: Int): IO[Option[Movie]]

trait MovieServiceComponent { this: MovieRepoComponent  =>
  val movieRepo: MovieRepo

  class MovieService:
    def getMovie(id: Int): IO[Option[Movie]] = movieRepo.getMovie(id)
}
```

MovieRepoComponentの実装クラスは、たとえばプロダクト環境用として以下のように実装される。これも入れ子になっている。

```scala
trait MovieRepoComponentImpl extends MovieRepoComponent:
  class MovieRepoImpl extends MovieRepo:
    def getMovie(id: Int): IO[Option[Movie]] = IO(db.get(id))
```

これらのコンポーネントを用途に応じて"wiring"してから実行することになるが、ここではサンプル通りに`object Registry`で実装した。なんとなく不思議で面白い。

```scala
object Registry extends MovieServiceComponent with MovieRepoComponentImpl:
  val movieRepo    = new MovieRepoImpl
  val movieService = new MovieService

val program = Registry.movieService.getMovie(42)

// rumtime -------------------------------------------------
program.unsafeRunSync() // Some(Movie(42,A Movie))
```
テストのときも同様にして、別の`*Impl`と`*Registry`を定義する。

```scala
trait MovieRepoComponentTestImpl extends MovieRepoComponent:
  class MovieRepoTestImpl extends MovieRepo:
    def getMovie(id: Int): IO[Option[Movie]] = IO(Movie(-1, "Test").some)

object TestRegistry
    extends MovieServiceComponent with MovieRepoComponentTestImpl:

  val movieRepo    = new MovieRepoTestImpl
  val movieService = new MovieService

val testProgram = TestRegistry.movieService.getMovie(42)
testProgram.unsafeRunSync() 
// Some(Movie(-1,Test))
```
ソース: [original_cake.worksheet.sc](https://github.com/yasuabe/qiita-cats3-sample/blob/main/src/main/scala/di/original_cake.worksheet.sc)

### Minimal Cake Pattern

Minimal Cake Pattern では、オリジナル Cake パターンのパラレル入れ子構造はなくなる。まずインジェクト対象のコンポーネントの trait、及びそれとペアになる`Uses*`を定義する。

```scala
trait MovieRepo:
  def getMovie(id: Int): IO[Option[Movie]]

trait UsesMovieRepo:
  val movieRepo: MovieRepo
```

さらに使用したいコンポーネントの実装と、それを使用側で関連づけるための`MixIn*`も定義する。

```scala
object MovieRepoImpl extends MovieRepo:
  def getMovie(id: Int): IO[Option[Movie]] = IO(dB.get(id))

trait MixInMovieRepo:
  val movieRepo: MovieRepo = MovieRepoImpl
```

コンポーネント-`Uses*`のペアと、実装-`MixIn*`のペアの間に見られる「I/F と実装の分離」は、コンポーネントを使用する側にも反映される。

```scala
trait MovieService extends UsesMovieRepo:
  def getMovie(id: Int): IO[Option[Movie]] = movieRepo.getMovie(id)

object MovieService extends MovieService with MixInMovieRepo
val program = MovieService.getMovie(42)
```
テスト用のコンポーネントも以下のように簡単にインジェクトできる。ここではやらなかったがモックテストもスムーズに書ける。

```scala
val testMovieService: MovieService = new:
  val movieRepo: MovieRepo = new:
    def getMovie(id: Int): IO[Option[Movie]] = IO(Movie(-1, "Test").some)

val testProgram = testMovieService.getMovie(42)
testProgram.unsafeRunSync()
```

ちなみに Scala初心者の頃に入ったチームでこのパターンを知ったとき、「ふーんScalaはこうやるんだー」くらいの感じで軽く流してしまってたけど、初心者でも迷わず自然に使い始められるほどシンプルなのに、DIでやりたいことの8〜9割くらいできてしまうのが今思えばすごい。

ソース: [min_cake.worksheet.sc](https://github.com/yasuabe/qiita-cats3-sample/blob/main/src/main/scala/di/min_cake.worksheet.sc)

### ReaderT Monad
関数型プログラミング界隈では`Reader`モナドを使ったイディオムも昔からよく紹介されている。これもいろんな実装方法があるが、ここでは上の Minimum Cake Pattern のコードをベースにして`Uses*`を踏襲しつつ、`MixIn*`を`ReaderT`の文脈上の`Env`に替えてみた。

```scala
trait MovieServiceEnv extends UsesMovieRepo
```

使用する側ではDIするコンポーネントを`*Env`の実装で wiring して、`ReaderT#run`に渡す。

```scala
object MovieServiceEnv extends MovieServiceEnv:
  val movieRepo = MovieRepoImpl

object MovieService:
  def getMovie(id: Int): ReaderT[IO, MovieServiceEnv, Option[Movie]] =
    ReaderT(_.movieRepo.getMovie(id))

val task1 = MovieService.getMovie(42).run(MovieServiceEnv)
```

テストも同様。

```scala
object TestEnvironment extends MovieServiceEnv:
  val movieRepo: MovieRepo = new:
    def getMovie(id: Int): IO[Option[Movie]] = IO(Movie(-1, "Test").some)

val task2 = MovieService.getMovie(42).run(TestEnvironment)
task2.unsafeRunSync() //res1: Option[Movie] = Some(Movie(-1,Test))
```
（やってみて気づいたが、結局 Minimum Cake Patternと同じというか、むしろ一手間増えているだけかもしれない。）
ソース: [DI_reader.worksheet.sc](https://github.com/yasuabe/qiita-cats3-sample/blob/main/src/main/scala/di/DI_reader.worksheet.sc)

### Free Monad
ここまで書籍のサンプルコードに付け加えた`IO`に触れなかったが、`IO`がドメイン層まで入ってきていると、モデルの**表現**と**実行**という2つの異なる**関心事が癒着**してしまっていて、実行の「文脈」を与えるモナドへの依存関係が生じている。この「依存」も純粋なドメインモデルと切り離し、癒着を解いて[シンプル](https://www.infoq.com/presentations/Simple-Made-Easy)にしたい。このために `Free` モナドが使える。

Free モナドの場合 ADT とインタープリターを定義するが、まず ADT は以下のようにしてみた（実際はどのレイヤーで定義するかとか、どのくくりでまとめるとか設計ポイントがあるが、ここでは適当にした）。

```scala
case class Movie(id: Int, title: String)
enum Query[A]:
  case GetMovie(id: Int) extends Query[Option[Movie]]
```
`Free`モナドの定義は以下のようになる。ADT を`Free`にリフトする定型的なブリッジも必要になる[^freestyle]。

[^freestyle]: [Freestyle](http://frees.io/) などを使ってこのボイラープレートを減らす方法もあるがここでは割愛する。

```scala
type QueryF[T] = Free[Query, T]

object MovieRepoOps:
  def getMovie(id: Int): QueryF[Option[Movie]] = liftF(GetMovie(id))
```

これらを使ってドメインロジック(ここでは簡単のため`getMovie`呼び出しのみ)を組み立てるコードは以下のようになる。

```scala
object MovieService:
  def getMovie(id: Int): QueryF[Option[Movie]] = MovieRepoOps.getMovie(id)
```
ADTから`Task`への「自然変換[^natural_translformation]」(`cats.arrow.FunctionK`)は以下のように書く。模擬DBへの依存もここに置いた。

```scala
def taskInterpreter: Query ~> IO = new:
  def apply[A](fa: Query[A]): IO[A] = fa match
    case GetMovie(id) => IO(db.get(id))
```

実行は`Free#foldMap`にインタープリターを渡す。

```scala
val program = MovieService.getMovie(42).foldMap(taskInterpreter)

program.unsafeRunSync() // Some(Movie(42, A Movie))
```

テストも同じように書ける。ここではインタープリターの変換先を`Id`としてみたが、呼び出しを記録したければ`Writer`モナドが使えるし、インタラクションを含めたシナリオを定義したければ `State` などを使えばいい。

```scala
def testInterpreter: Query ~> Id = new:
  def apply[A](fa: Query[A]): Id[A] = fa match
    case GetMovie(_: Int) => Option(Movie(-1, "Test"))

val id1 = MovieService.getMovie(42).foldMap(testInterpreter)
// Some(Movie(-1,Test))
```
ソース: [DI_free.worksheet.sc](https://github.com/yasuabe/qiita-cats3-sample/blob/main/src/main/scala/di/DI_free.worksheet.sc)

### Free + ReaderT Monad
モナドが提供する実行の文脈を差し替える`Free`モナドと、コンポーネントの wiring を差し替える`Reader`モナドを併用することもできる。

```scala
trait MovieRepo:
  def getMovie(id: Int): QueryF[Option[Movie]]

trait Env:
  def movieRepo: MovieRepo

type QueryF[T] = Free[Query, T]
type EnvReader[T] = ReaderT[QueryF, Env, T]

object MovieRepoOps:
  def getMovie(id: Int): QueryF[Option[Movie]] = Free.liftF(GetMovie(id))
```

コンポーネントを使う側の`MovieService`では、`Reader`の文脈上の`Env.movieRepo`をリポジトリ実装として使う。

```scala
object MovieService:
  def getMovie(id: Int): EnvReader[Option[Movie]] =
    ReaderT (_.movieRepo.getMovie(id))
```

`Query~>Task`のインタープリターは上で書いたもの同じ。

`Reader`のときと同様に`Env`を定義するが、ここでは`MovieRepo`の実装を`Env`の中で書いた。

```scala
val productEnv: Env = new:
  def movieRepo: MovieRepo = id => MovieRepoOps.getMovie(id)
```

実行は以下のようになる。コンポーネントの wiring を定めた`productEnv`と、Taskへの自然変換を定めた`taskInterpreter`を個別に与えている。

```scala
val program = MovieService.getMovie(42)
                          .run(productEnv)
                          .foldMap(taskInterpreter)
```

テストでは固定値を返すダミーの`MovieRepo`を`Env`上でコンポーネント実装に指定して、`ADT`を記録する`Writer`をモナドに指定した。

```scala
type TestWriter[T] = Writer[Vector[Any], T]
def testInterpreter2: Query ~> TestWriter = new:
   def apply[A](q: Query[A]): TestWriter[A] = q match
     case GetMovie(id) => Option(Movie(1, "A Movie")).writer(Vector(q))

val testEnv: Env = new:
  def movieRepo: MovieRepo = _ => MovieRepoOps.getMovie(-1)

val (l, v) = MovieService.getMovie(-1)
                         .run(testEnv)
                         .foldMap(testInterpreter2)
                         .run
// l: Vector[Any] = Vector(GetMovie(-1))
// v: Option[Movie] = Some(Movie(1,A Movie))
```

実際には、Functionalテストでは`Env`上のコンポーネントを本物に wiring したまま自然変換のみテスト用に替えて、Unitテストでは隣接するコンポーネントをモックに差し替えるような使い分けになると思う。
ソース: [DI_freeT2.worksheet.sc](https://github.com/yasuabe/qiita-cats3-sample/blob/main/src/main/scala/di/DI_freeT2.worksheet.sc)

### ZIO
「記述と実行の分離」から一旦離れてみる。

以前 [ZIO の紹介記事](https://qiita.com/yasuabe2613/items/0146887d115df00ef56f)を書いたが、`ZIO[R, E, A]` とは簡単に言えば Cats Effect の `IO[A]` に環境 `R` とエラー `E` を加えた、謂わば *trifunctor* とでも言えそうな形式の型だった。`ReaderT` 同様、この環境 `R` に `Env` を与えることで DIっぽく使うことができる。

リポジトリアクセスで結果が得られなかった場合をエラー扱いとし `AppError`型で表現すれば、最終的な 型は `ZIO[Env, AppError, ?]` のようになる。ただしこのサンプルのリポジトリでは、それ自体は環境が要らないので、`ZIO[Any, E, A]` のエイリアス `IO[+E, +A]` を使う。

```scala
enum AppError:
  case NoValue extends AppError

trait MovieRepo:
  def getMovie(id: Int): IO[AppError, Movie] // = ZIO[Any, AppError, Movie]

object MovieRepoImpl extends MovieRepo:
  def getMovie(id: Int): IO[AppError, Movie] =
    ZIO.fromEither(db.get(id).toRight(NoValue))
```
環境 `Env` は以下のようにインターフェイスと実装を分離して、あとで差し替えられるようにしておく。

```scala
trait Env:
  val movieRepo: MovieRepo

object Env extends Env: // 製品コードの実装
  val movieRepo = MovieRepoImpl
```
ZIO の文脈内で環境にアクセスするには `ZIO.environment` を、環境を与えるには `Runtime#withEnvironment`が使える。まとめると以下のように実行できる。

```scala
object MovieService:
  def getMovie(id: Int): ZIO[Env, AppError, Movie] =
    ZIO.environment[Env].flatMap(_.get[Env].movieRepo.getMovie(id))

val program: ZIO[Env, AppError, Movie] = MovieService.getMovie(42)

val runtime = Runtime.default.withEnvironment(ZEnvironment(Env))
Unsafe.unsafe(runtime.unsafe.run(program))
// Success(Movie(42,A Movie))
```
テストは例えば以下のように書ける。

```scala
val testEnv: Env = new:
   val movieRepo = _ => ZIO.fromEither(NoValue.asLeft)

val testRuntime = Runtime.default.withEnvironment(ZEnvironment(testEnv))
Unsafe.unsafe(testRuntime.unsafe.run(program))
// Failure(Fail(NoValue))
```
実行のためのモナドが ZIO に固定されてしまう事[^zio_only]について妥協すれば、外観上はかなりシンプルに DI（≒オブジェクトのグラフの生成） が書ける。

ソース: [di_with_zio.worksheet.sc](https://github.com/yasuabe/qiita-cats3-sample/blob/main/src/main/scala/di/%20di_with_zio.worksheet.sc)

[^zio_only]: Cats Effect IO や Monix Task ではなく ZIO に固定される。

## 所感
- `IO`や`Task`などプログラムの「実行」に強く関わるモナドがあるとき、`Free`とモナドトランスフォーマーを用いると「関心事の分離」がやりやすい。
- 逆に、`IO`や`Task`など「実行系」のモナドがなかったり、あるいはそもそも「関心事の分離」に興味がない場合などは、`Free`のコストが割高になるかもしれない。
- 最後、Free + ReaderTで終わったが、今回は試さなかった Minimum Cake + Free の組み合わせの方がいいかもしれない。→ [書いた(2018/04/02)](https://qiita.com/yasuabe2613/items/5b816a125f073ff01932)
- `Free`の他に`Tagless final`などの選択肢も要検討。→ [書いた(2018/04/02)](https://qiita.com/yasuabe2613/items/5b816a125f073ff01932)
- 2019/04/25 に ZIO を追記した。[simple と easy を二項対立](https://www.infoq.com/presentations/Simple-Made-Easy)と見ると、記述と実行を分離した Free+ReaderT などと比べて、どちらかというと ZIO は easy 寄りで、その分微妙に complect[^complect] が生じている気がする。なので、すでに `Future`、`IO`、`Task` をベタ書きしているようなプログラムで関数型DIを導入するなら ZIO が良いかもしれないが、逆に実行の文脈を抽象化して関心事の分離と simplicity を徹底したいコード（特にドメイン層など）では、Free Monad や [Tagless Final](https://qiita.com/yasuabe2613/items/5b816a125f073ff01932) を使うと、純粋にドメインの意図が表現できるかもしれない。
- Scala 3 とその他最新のライブラリに合わせて修正した（2022-08-09）

[^complect]: 編み合わせる、織り合わせるといった意味で、[Simple made Easy](https://www.infoq.com/presentations/Simple-Made-Easy) では、プログラムの要素を解きほぐしにくい形で癒着させて、simple さを損ねるものとされている。

## 参考

- [実戦での Scala: Cake パターンを用いた Dependency Injection (DI) ](http://eed3si9n.com/ja/real-world-scala-dependency-injection-di)
- [Scalaにおける最適なDependency Injectionの方法を考察する 〜なぜドワンゴアカウントシステムの生産性は高いのか〜](https://qiita.com/pab_tech/items/1c0bdbc8a61949891f1f)
[^natural_translformation]:慣習上「自然変換」と言われることが多いが厳密には圏論の自然変換ではない。