by
Boris V.Kuznetsov
Adam Fraser
Chisel Crew, July 2020
About this talk
More Info here
Boris V. Kuznetsov
Adam Fraser
Dependency injection problem
Which are problems with all those?
ZIO Solution
ZIO[-R, +E, +A]
equivalent
R => Either[E,A]
ZIO is a functional data structure, which describes a computation that requires R on input, may fail with an error of type E (Throwable, String, Custom) or yield exactly one value of type A
More info here
Dependency injection with ZLayer
class Kafka {
def talk() = "kafka"
}
class Cassandra {
def talk() = "cassandra"
}
class Elastic {
def talk() = "elastic"
}
Dependency injection with ZLayer
object moduleA {
// service binding
type ModuleA = Has[ModuleA.Service]
// service declaration
object ModuleA {
trait Service {
def run(): UIO[String]
}
}
// service implementation
val live = ZLayer.fromService { kafka: Kafka =>
new Service {
override def run() = UIO(kafka.talk())
}
}
// Public accessor
def run(): URIO[ModuleA, String] = ZIO.accessM(_.get.run())
}Dependency injection with ZLayer
object moduleB {
// service binding
type ModuleB = Has[ModuleB.Service]
// service declaration
object ModuleB {
trait Service {
def run(): UIO[String]
}
}
// service implementation
val live = ZLayer.fromServices((modA: ModuleA.Service, cass: Cassandra) =>
new Service {
def run(): UIO[String] = modA.run().map(_ + "_" + cass.talk())
}
)
// Public accessor
def run(): URIO[ModuleB, String] = ZIO.accessM(_.get.run())
}Dependency injection with ZLayer
object BaseSpec extends DefaultRunnableSpec {
def spec =
suite("ZLayerSpec")(
testM("Module A test") {
val res = moduleA.run()
assertM(res)(equalTo("kafka"))
}.provideCustomLayer(liveLayerA),
testM("Module B test") {
val res = moduleB.run()
assertM(res)(equalTo("kafka_cassandra"))
}.provideCustomLayerShared(liveLayer)
)
val kafkaLayer = ZLayer.succeed(new Kafka {})
val cassLayer = ZLayer.succeed(new Cassandra {})
val liveLayerA = kafkaLayer >>> moduleA.live
val liveLayer = (liveLayerA ++ cassLayer) >>> moduleB.live
}ZLayer benefits
The magic of zio.Has
final class Has[A] private (
private val map: Map[LightTypeTag, scala.Any],
private var cache: Map[LightTypeTag, scala.Any] = Map()
) extends Serializable {...}More on Izumi here
Type tagging
// defines a phantom type
// which exists compile time only
// No runtime information
trait A
// defines Int.
// Known both at compile time and runtime
// Maps to JVM primitive
val number = 4
// object of type A
// requires allocation since is not mapped on JVM primitive
val aVale = new A {}
// Defines a value of a type, which is
// Int with A at compile time
// Int in run time
val extNumber = number.asInstanceOf[Int with A]Type Tagging with Shapeless
import shapeless.labelled.{KeyTag, FieldType}
import shapeless.syntax.singleton._
// Int
val number = 11
// Enrich the Int type with a string type with signature "positiveNumber"
val numberAndString = "positiveNumber" ->> number
// numberAndString: Int with shapeless.labelled.KeyTag[String("positiveNumber"),Int] = 11
Heterogenous Map
Has[Blocking.Service] with
Has[Random.Service] with
Has[Clock.Service] with
Has[Console.Service] with
Has[System.Service]
Map(
Tagged[Blocking.Service] -> BlockingServiceLive,
Tagged[Random.Service] -> RandomServiceLive,
Tagged[Clock.Service] -> ClockServiceLive,
Tagged[Console.Service] -> ConsoleServiceLive,
Tagged[System.Service] -> SystemServiceLive
)
Izumi reflect
More info here
Izumi Reflect
trait Super
trait Child extends Super
assert(
Tag[Either[RuntimeException, Child]].tag <:<
Tag[Either[Throwable, Super]].tag
)
assert(
Tag[Either[RuntimeException, Child]].tag =:=
TagKK[Either].tag.combine(Tag[RuntimeException].tag, Tag[Child].tag)
)
assert(
Tag[Either[RuntimeException, Child]].tag <:<
TagKK[Either].tag.combine(Tag[Throwable].tag, Tag[Super].tag)
)Izumi Reflect
Caveats
Thank you!