pexels-photo-1591447.jpeg

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

 
0
このエントリーをはてなブックマークに追加
Kazuki Moriyama
Kazuki Moriyama (森山 和樹)

はじめに

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きもちい。

info-outline

お知らせ

K.DEVは株式会社KDOTにより運営されています。記事の内容や会社でのITに関わる一般的なご相談に専門の社員がお答えしております。ぜひお気軽にご連絡ください。