ZIOへの環境Rのprovide方法各種

はじめに

ScalaのライブラリZIOにはDIを扱うための仕組みが組み込みで備わっている。
DIの実態をprovideする方法はいくつかあるので紹介する。
なおZIOの基本的な理解は所与とする。
DIを補助する仕組みとしてのZLayerHasについてはこの記事がよくまとまっている。

provide

ZIO#provideメソッドはZIO[R, _, _]として依存しているRをすべて解決する。

trait Logger { ... }
trait DB { ... }

val z: RIO[Logger with DB, Unit] = ...
val env = new Logger with DB { ... }

z.provide(env)
// => ZIO[Any, Throwable, Unit]

依存はAnyとなりどのコンポーネントにも依存しない実行可能なZIOが生成される。

ちなみにすでに依存が存在しないprovideする必要のないZIO[Any, _, _]に対してprovideを呼ぶとコンパイルが通らない。
よくできている。
仕組みは簡単で、NeedsEnv[T]というimplicit parameterをprovideメソッドに付与し、Anyに対してのみわざと2つの同じ優先度を持つインスタンスを配置することで
implicit ambiguousを起こしている。

sealed trait ZIO[-R, +E, +A] extends Serializable with ZIOPlatformSpecific[R, E, A] { self =>
  def provide(r: R)(implicit ev: NeedsEnv[R]): IO[E, A] = ...
}

sealed abstract class NeedsEnv[+R]

object NeedsEnv extends NeedsEnv[Nothing] {
  // R =:= Any以外のときには問答無用でインスタンスを提供する
  implicit def needsEnv[R]: NeedsEnv[R] = NeedsEnv

  // R =:= Anyのときにはわざと2つインスタンスを配置しimplicit ambiguous 
  implicit val needsEnvAmbiguous1: NeedsEnv[Any] = NeedsEnv
  implicit val needsEnvAmbiguous2: NeedsEnv[Any] = NeedsEnv
}

provideSome

前述のprovideメソッドでは依存Rを一気に解決する必要があったが、部分的に解決できたほうが便利なことも多い。
provideSomeを使用することで部分的な解決が可能になる。

val z: RIO[Logger with DB, Unit] = ...

z.provideSome[DB](db =>
  new Logger with DB {
    // implementations using given DB instance
  }
)
// => ZIO[DB, Throwable, Unit]

DBを与えられたと仮定した場合のすべての依存の実装を提供することでDBにのみ依存するZIOが作られる。
注意しなければならないのが型パラメータでprovideSome[DB]のように依存させるサービスを宣言しなければならない。

またprovideのようにZIO[Any, _, _]に対してprovideSomeを呼んでもコンパイルが通らない。

provideLayer

zioで依存を管理するときはZLayerを使ったmoduleパターンで設計することが典型
provideLayerを使用することでZLayerZIOに依存として提供することができる。

val z: RIO[Has[Logger] with Has[DB], Unit] = ...
val layer = ZLayer.succeedMany(Has.allOf(new Logger { ... }, new DB { ... }))

z.provideLayer(layer)
// => ZIO[Any, Throwable, Unit]

ただし一気にすべてのコンポーネントを提供しなければならない。

provideSomeLayer

provideSomeのように部分的にZLayerの依存を解決できる。

val z: RIO[Has[Logger] with Has[DB], Unit] = ...
val layer = ZLayer.succeed(new Logger { ... })

z.provideSomeLayer[Has[DB]](layer)
// => ZIO[Has[DB], Throwable, Unit]

provideSomeの様に解決後も残ってしまう依存の型をパラメタで宣言しなければならない。

provideCustomLayer

provideSomeLayerの特化版。
依存解決後も残ってしまう型がZEnvに固定されたもの。
zio.Apptraitを使用したzioアプリケーションの実行時にはZEnvの依存はデフォルト値が与えられるため、そのようなときに有用。

val z: RIO[Has[Logger] with Has[DB] with ZEnv, Unit] = ...
val layer = ZLayer.succeedMany(Has.allOf(new Logger { ... }, new DB { ... }))

z.provideCustomLayer(layer)
// => ZIO[ZEnv, Throwable, Unit]

ZEnv以外の依存はすべて与えなければならない。

おわり

zioきもちい。

ScalaのAuxパターン cats-effectv3.0.0がリリースされたらしい リテラル型(literal type)
View Comments
There are currently no comments.