IZUMI 1.0
YOUR NEXT SCALA STACK
INTRO
WHO WE ARE
▸ Pavel and Kai
▸ Septimal Mind, an Irish consultancy
▸ Engineer’s productivity is our primary concern
▸ Love Scala and pure FP
▸ Scala contributors
INTRO
WHAT WE DO
▸ We identify productivity issues in SDLC pipeline
▸ We build tools to address these issues
INTRO
WHAT WE USE
‣ We've been using Scala for many years
‣ Bifuctors are beneficial, we've been using Scalactic
‣ We've adopted ZIO since first public alpha
‣ It closed the everlasting question of error encoding
INTRO
OPEN QUESTION
How do we design
complex but extensible
FP applications?
(Hundreds/thousands of components)
INTRO
THE ANSWER
Modularity and modules
INTRO
HOW TO GET THERE?
‣ Dependency Injection
‣ Tagless Final
IZUMI
WE MADE OUR OWN "NANOFRAMEWORK", IZUMI
‣ DIStage: Advanced Dependency Injection
‣ BIO: Tagless Final for Bifunctors and Trifunctors
Also:
‣ LogStage: Zero-Effort Structural logging
‣ etc
BIO
WHAT IS BIO?
‣ A set of typeclasses for Bifunctors and Trifunctors
‣ Like Cats but for F[+_, +_] and F[-_, +_, +_]
‣ Should be incorporated into ZIO Prelude in foreseeable
future
DISTAGE
WHAT IS DISTAGE?
‣ Your regular Dependency Injection library
‣ Supports F[_]
‣ Unique feature: configurable applications
‣ Since 1.0: State-of-the-art compile-time verification
‣ Many features: resources/lifecycles, integration checks, etc
‣ The most advanced DI/Module system available
IZUMI 1.0: DISTAGE
WHAT IS A CONFIGURABLE APP?
‣ An application may run in several "modes"
‣ E.g. Purpose={Test|Prod}, Database={Dummy|Postgres}
‣ Modes can be chosen at application startup
‣ Modes may be combined
‣ The app should choose right component implementations
‣ And block incorrect combinations
IZUMI 1.0: DISTAGE
WHAT IS A CONFIGURABLE APP?
‣ Purpose = Production | Test
‣ Database = Postgres | Oracle | Dummy
‣ We may want to run tests with Postgres database (but never with Oracle)
‣ We want to never run tests with production payment service
‣ We may want to define defaults
‣ Database is not set for Prod run => use Postgres
IZUMI 1.0: DISTAGE
CONFIGURABLE APP: HOW?
There was no good way to write configurable apps
IZUMI 1.0: DISTAGE
CONFIGURABLE APP: HOW?
...hard to code, hard to maintain...
...exponential amount of code paths...
...edgecases...
IZUMI 1.0: DISTAGE
CONFIGURABLE APP: HOW?
‣ It's hard to do it
‣ ... even if you don't have transitive dependencies
‣ ... even if you don't have dependent constraints
‣ It's even harder to provide compile-time guarantees
IZUMI 1.0: DISTAGE
CONFIGURABLE APP: HOW?
def makeUserRepo(config: Config): IUserRepo[F] = {
config.database match {
case OracleDb =>
if (!config.isProd) {
throw new RuntimeException("Oracle unsupported in test mode!")
} else {
new ProdOracleUserRepo[F]( /*...*/ )
}
case PgDb =>
new ProdPGUserRepo[F]( /*...*/ )
case Unset =>
if (config.isProd) {
throw new RuntimeException("Database is not set for prod mode!")
} else {
new DummyUserRepo[F]()
}
}
}
IZUMI 1.0: DISTAGE
CONFIGURABLE APP: HOW?
DIStage made it possible
Though it was doing things in runtime...
We thought compile-time solution is impossible...
IZUMI 1.0: DISTAGE
CONFIGURABLE APP: HOW?
DIStage since Izumi 1.0:
strong compile-time guarantees
for
configurable apps
IZUMI 1.0: DISTAGE
CONFIGURABLE APP WITH DISTAGE
class MyAppDefinition[F[+_, +_]: TagKK] extends PluginDef {
make[EnterpriseApp[F]]
make[AccountsService[F]]
make[UsersService[F]]
make[IUserRepo[F]].from[DummyUserRepo[F]].tagged(Mode.Test)
make[IUserRepo[F]].from[ProdPGUserRepo[F]].tagged(Db.Pg)
make[IUserRepo[F]].from[ProdOracleUserRepo[F]].tagged(Mode.Prod, Db.Oracle)
make[IAccountsRepo[F]].from[DummyAccRepo[F]].tagged(Mode.Test)
make[IAccountsRepo[F]].from[ProdPGAccRepo[F]].tagged(Db.Pg)
make[IAccountsRepo[F]].from[ProdOracleAccRepo[F]].tagged(Mode.Prod, Db.Oracle)
make[IPaymentsGateway[F]].from[DummyPayments[F]].tagged(Mode.Test)
make[IPaymentsGateway[F]].from[StripePayments[F]].tagged(Mode.Prod)
}
IZUMI 1.0: DISTAGE
COMPILE-TIME SAFETY
// your main method
object EnterpriseMain extends RoleAppMain.LauncherBIO[zio.IO] { ... }
// in test scope
object WiringTest extends PlanCheck.Main(EnterpriseMain)
IZUMI 1.0: DISTAGE
COMPILE-TIME SAFETY
// your main method
object EnterpriseMain extends RoleAppMain.LauncherBIO[zio.IO] { ... }
// in test scope
object WiringTest extends PlanCheck.Main(EnterpriseMain)
HOW DOES IT WORK?
IZUMI 1.0: DISTAGE
COMPILE-TIME SAFETY
// your main method
object EnterpriseMain extends RoleAppMain.LauncherBIO[zio.IO] { ... }
// in test scope
object WiringTest extends PlanCheck.Main(EnterpriseMain)
HOW DOES IT WORK?
abstract class PluginDef[T](implicit recompilationToken: RecompilationToken[T])
IZUMI 1.0: BIO
BIO TYPECLASSES
IZUMI 1.0: BIO
BIO SUMMONER
import izumi.functional.bio.F
class DummyUserRepo[F[+_, +_]: Monad2]
F.pure(...)

F.fail(...)

F.fromEither(...)
Error2[F].fail(...)
See also: https://www.youtube.com/watch?v=ZdGK1uedAE0&t=580s
IZUMI 1.0: BIO
BIO SYNTAX
import izumi.functional.bio.{F, Error2}
// IDE auto imports
def launchMissiles[F[+_, +_]: Error2](target: Target) =
for {
_ <- F.unless(storage.hasMissiles)(F.fail(AmmoDepleted))
_ <- loadMissile(launcher, storage)
_ <- fireMissile(launcher)
.catchAll(_ => emergencyStop)
_ <- F.unless(target.isAnnihilated)(launchMissiles(target))
} yield res
import cats.syntax.all._
import cats.effect.syntax.all._
import monix.catnap.syntax._
IZUMI 1.0: BIO
COMPATIBILITY
import izumi.functional.bio.catz._
def http4sServer[F[+_, +_]: Async2: Fork2: UnsafeRun2] = {
BlazeServerBuilder[F[Throwable, ?]](ec)
.bindHttp(8080)
.withSslContext(sslContext)
.resource
}
ConcurrentEffect[F[Throwable, ?]]
MonadError[F[Throwable, ?], Throwable]
IZUMI 1.0: SUMMARY
CONCLUSION
‣ BIO provides a great set of TF abstractions
‣ DIStage is great for global/static contexts
‣ and ZIO's reader is perfect for local/dynamic contexts
‣ Izumi 1.0+ZIO is the most productive Scala stack
‣ And battle-tested
‣ There is no excuse not to use DIStage anymore
‣ Consider it for your next Scala project
Thank you!
https://github.com/7mind/izumi
https://github.com/7mind/distage-example
https://twitter.com/shirshovp
https://twitter.com/kai_nyasha

Izumi 1.0: Your Next Scala Stack

  • 1.
  • 2.
    INTRO WHO WE ARE ▸Pavel and Kai ▸ Septimal Mind, an Irish consultancy ▸ Engineer’s productivity is our primary concern ▸ Love Scala and pure FP ▸ Scala contributors
  • 3.
    INTRO WHAT WE DO ▸We identify productivity issues in SDLC pipeline ▸ We build tools to address these issues
  • 4.
    INTRO WHAT WE USE ‣We've been using Scala for many years ‣ Bifuctors are beneficial, we've been using Scalactic ‣ We've adopted ZIO since first public alpha ‣ It closed the everlasting question of error encoding
  • 5.
    INTRO OPEN QUESTION How dowe design complex but extensible FP applications? (Hundreds/thousands of components)
  • 6.
  • 7.
    INTRO HOW TO GETTHERE? ‣ Dependency Injection ‣ Tagless Final
  • 8.
    IZUMI WE MADE OUROWN "NANOFRAMEWORK", IZUMI ‣ DIStage: Advanced Dependency Injection ‣ BIO: Tagless Final for Bifunctors and Trifunctors Also: ‣ LogStage: Zero-Effort Structural logging ‣ etc
  • 9.
    BIO WHAT IS BIO? ‣A set of typeclasses for Bifunctors and Trifunctors ‣ Like Cats but for F[+_, +_] and F[-_, +_, +_] ‣ Should be incorporated into ZIO Prelude in foreseeable future
  • 10.
    DISTAGE WHAT IS DISTAGE? ‣Your regular Dependency Injection library ‣ Supports F[_] ‣ Unique feature: configurable applications ‣ Since 1.0: State-of-the-art compile-time verification ‣ Many features: resources/lifecycles, integration checks, etc ‣ The most advanced DI/Module system available
  • 11.
    IZUMI 1.0: DISTAGE WHATIS A CONFIGURABLE APP? ‣ An application may run in several "modes" ‣ E.g. Purpose={Test|Prod}, Database={Dummy|Postgres} ‣ Modes can be chosen at application startup ‣ Modes may be combined ‣ The app should choose right component implementations ‣ And block incorrect combinations
  • 12.
    IZUMI 1.0: DISTAGE WHATIS A CONFIGURABLE APP? ‣ Purpose = Production | Test ‣ Database = Postgres | Oracle | Dummy ‣ We may want to run tests with Postgres database (but never with Oracle) ‣ We want to never run tests with production payment service ‣ We may want to define defaults ‣ Database is not set for Prod run => use Postgres
  • 13.
    IZUMI 1.0: DISTAGE CONFIGURABLEAPP: HOW? There was no good way to write configurable apps
  • 14.
    IZUMI 1.0: DISTAGE CONFIGURABLEAPP: HOW? ...hard to code, hard to maintain... ...exponential amount of code paths... ...edgecases...
  • 15.
    IZUMI 1.0: DISTAGE CONFIGURABLEAPP: HOW? ‣ It's hard to do it ‣ ... even if you don't have transitive dependencies ‣ ... even if you don't have dependent constraints ‣ It's even harder to provide compile-time guarantees
  • 16.
    IZUMI 1.0: DISTAGE CONFIGURABLEAPP: HOW? def makeUserRepo(config: Config): IUserRepo[F] = { config.database match { case OracleDb => if (!config.isProd) { throw new RuntimeException("Oracle unsupported in test mode!") } else { new ProdOracleUserRepo[F]( /*...*/ ) } case PgDb => new ProdPGUserRepo[F]( /*...*/ ) case Unset => if (config.isProd) { throw new RuntimeException("Database is not set for prod mode!") } else { new DummyUserRepo[F]() } } }
  • 17.
    IZUMI 1.0: DISTAGE CONFIGURABLEAPP: HOW? DIStage made it possible Though it was doing things in runtime... We thought compile-time solution is impossible...
  • 18.
    IZUMI 1.0: DISTAGE CONFIGURABLEAPP: HOW? DIStage since Izumi 1.0: strong compile-time guarantees for configurable apps
  • 19.
    IZUMI 1.0: DISTAGE CONFIGURABLEAPP WITH DISTAGE class MyAppDefinition[F[+_, +_]: TagKK] extends PluginDef { make[EnterpriseApp[F]] make[AccountsService[F]] make[UsersService[F]] make[IUserRepo[F]].from[DummyUserRepo[F]].tagged(Mode.Test) make[IUserRepo[F]].from[ProdPGUserRepo[F]].tagged(Db.Pg) make[IUserRepo[F]].from[ProdOracleUserRepo[F]].tagged(Mode.Prod, Db.Oracle) make[IAccountsRepo[F]].from[DummyAccRepo[F]].tagged(Mode.Test) make[IAccountsRepo[F]].from[ProdPGAccRepo[F]].tagged(Db.Pg) make[IAccountsRepo[F]].from[ProdOracleAccRepo[F]].tagged(Mode.Prod, Db.Oracle) make[IPaymentsGateway[F]].from[DummyPayments[F]].tagged(Mode.Test) make[IPaymentsGateway[F]].from[StripePayments[F]].tagged(Mode.Prod) }
  • 20.
    IZUMI 1.0: DISTAGE COMPILE-TIMESAFETY // your main method object EnterpriseMain extends RoleAppMain.LauncherBIO[zio.IO] { ... } // in test scope object WiringTest extends PlanCheck.Main(EnterpriseMain)
  • 21.
    IZUMI 1.0: DISTAGE COMPILE-TIMESAFETY // your main method object EnterpriseMain extends RoleAppMain.LauncherBIO[zio.IO] { ... } // in test scope object WiringTest extends PlanCheck.Main(EnterpriseMain) HOW DOES IT WORK?
  • 22.
    IZUMI 1.0: DISTAGE COMPILE-TIMESAFETY // your main method object EnterpriseMain extends RoleAppMain.LauncherBIO[zio.IO] { ... } // in test scope object WiringTest extends PlanCheck.Main(EnterpriseMain) HOW DOES IT WORK? abstract class PluginDef[T](implicit recompilationToken: RecompilationToken[T])
  • 23.
    IZUMI 1.0: BIO BIOTYPECLASSES
  • 24.
    IZUMI 1.0: BIO BIOSUMMONER import izumi.functional.bio.F class DummyUserRepo[F[+_, +_]: Monad2] F.pure(...)
 F.fail(...)
 F.fromEither(...) Error2[F].fail(...) See also: https://www.youtube.com/watch?v=ZdGK1uedAE0&t=580s
  • 25.
    IZUMI 1.0: BIO BIOSYNTAX import izumi.functional.bio.{F, Error2} // IDE auto imports def launchMissiles[F[+_, +_]: Error2](target: Target) = for { _ <- F.unless(storage.hasMissiles)(F.fail(AmmoDepleted)) _ <- loadMissile(launcher, storage) _ <- fireMissile(launcher) .catchAll(_ => emergencyStop) _ <- F.unless(target.isAnnihilated)(launchMissiles(target)) } yield res import cats.syntax.all._ import cats.effect.syntax.all._ import monix.catnap.syntax._
  • 26.
    IZUMI 1.0: BIO COMPATIBILITY importizumi.functional.bio.catz._ def http4sServer[F[+_, +_]: Async2: Fork2: UnsafeRun2] = { BlazeServerBuilder[F[Throwable, ?]](ec) .bindHttp(8080) .withSslContext(sslContext) .resource } ConcurrentEffect[F[Throwable, ?]] MonadError[F[Throwable, ?], Throwable]
  • 27.
    IZUMI 1.0: SUMMARY CONCLUSION ‣BIO provides a great set of TF abstractions ‣ DIStage is great for global/static contexts ‣ and ZIO's reader is perfect for local/dynamic contexts ‣ Izumi 1.0+ZIO is the most productive Scala stack ‣ And battle-tested ‣ There is no excuse not to use DIStage anymore ‣ Consider it for your next Scala project
  • 28.