├── .gitignore ├── .scalafmt.conf ├── build.sbt ├── core └── src │ └── main │ └── scala │ ├── Logic.scala │ └── Main.scala └── project ├── build.properties └── plugins.sbt /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | .cache 5 | .history 6 | .lib/ 7 | dist/* 8 | target/ 9 | lib_managed/ 10 | src_managed/ 11 | project/boot/ 12 | project/plugins/project/ 13 | 14 | .idea* 15 | .bsp -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 3.5.3 2 | maxColumn = 100 3 | runner.dialect = scala3 4 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import com.softwaremill.SbtSoftwareMillCommon.commonSmlBuildSettings 2 | 3 | lazy val commonSettings = commonSmlBuildSettings ++ Seq( 4 | organization := "com.softwaremill.zio2", 5 | scalaVersion := "3.1.3" 6 | ) 7 | 8 | lazy val rootProject = (project in file(".")) 9 | .settings(commonSettings: _*) 10 | .settings(publishArtifact := false, name := "zio2-structure") 11 | .aggregate(core) 12 | 13 | lazy val core: Project = (project in file("core")) 14 | .settings(commonSettings: _*) 15 | .settings( 16 | name := "core", 17 | libraryDependencies ++= Seq( 18 | "dev.zio" %% "zio" % "2.0.0" 19 | ) 20 | ) 21 | -------------------------------------------------------------------------------- /core/src/main/scala/Logic.scala: -------------------------------------------------------------------------------- 1 | import zio.{Cause, durationInt, IO, Ref, Scope, Task, UIO, ULayer, URIO, ZIO, ZIOAppDefault, ZLayer} 2 | 3 | case class Connection(id: String) 4 | case class Car(make: String, model: String, licensePlate: String) 5 | case class LicensePlateExistsError(licensePlate: String) 6 | 7 | // 8 | 9 | class CarApi(carService: CarService): 10 | def register(input: String): ZIO[Any, Nothing, String] = 11 | val rid = "REQ" + Math.abs(input.hashCode % 10000) 12 | ZIO.logSpan(rid) { 13 | input.split(" ", 3).toList match 14 | case List(f1, f2, f3) => 15 | val car = Car(f1, f2, f3) 16 | carService.register(car).as("OK: Car registered").catchAll { 17 | case _: LicensePlateExistsError => 18 | ZIO 19 | .logError( 20 | s"Cannot register: $car, because a car with the same license plate already exists" 21 | ) 22 | .as("Bad request: duplicate") 23 | case t => 24 | ZIO 25 | .logErrorCause(s"Cannot register: $car, unknown error", Cause.fail(t)) 26 | .as("Internal server error") 27 | } 28 | case _ => ZIO.logError(s"Bad request: $input").as("Bad Request") 29 | } 30 | 31 | object CarApi: 32 | val live: ZLayer[CarService, Any, CarApi] = ZLayer.fromFunction(CarApi(_)) 33 | 34 | // 35 | 36 | class CarService(carRepository: CarRepository, db: DB): 37 | def register(car: Car): ZIO[Any, Throwable | LicensePlateExistsError, Unit] = 38 | db.transact { 39 | carRepository.exists(car.licensePlate).flatMap { 40 | case true => ZIO.fail(LicensePlateExistsError(car.licensePlate)) 41 | case false => carRepository.insert(car) 42 | } 43 | } 44 | 45 | object CarService: 46 | val live: ZLayer[CarRepository & DB, Nothing, CarService] = 47 | ZLayer.fromFunction(CarService(_, _)) 48 | 49 | // 50 | 51 | class CarRepository(): 52 | def exists(licensePlate: String): ZIO[Connection, Nothing, Boolean] = 53 | ZIO 54 | .service[Connection] 55 | .map(_ => /* perform the check */ licensePlate.startsWith("WN")) 56 | .tap(_ => ZIO.logInfo(s"Checking if license plate exists: $licensePlate")) 57 | .delay(100.millis) 58 | 59 | def insert(car: Car): ZIO[Connection, Nothing, Unit] = 60 | ZIO 61 | .service[Connection] 62 | .map(_ => /* perform the insert */ ()) 63 | .tap(_ => ZIO.logInfo(s"Inserting car: $car")) 64 | .delay(200.millis) 65 | 66 | object CarRepository: 67 | val live: ZLayer[Any, Nothing, CarRepository] = ZLayer.succeed(CarRepository()) 68 | 69 | // 70 | 71 | class DB(connectionPool: ConnectionPool): 72 | private def connection: ZIO[Scope, Throwable, Connection] = 73 | ZIO.acquireRelease(connectionPool.obtain)(c => 74 | connectionPool 75 | .release(c) 76 | .catchAll(t => ZIO.logErrorCause("Exception when releasing a connection", Cause.fail(t))) 77 | ) 78 | 79 | def transact[R, E, A](dbProgram: ZIO[Connection & R, E, A]): ZIO[R, E | Throwable, A] = 80 | ZIO.scoped { 81 | connection.flatMap { c => 82 | dbProgram.provideSomeLayer(ZLayer.succeed(c)) 83 | } 84 | } 85 | 86 | object DB: 87 | val live: ZLayer[ConnectionPool, Nothing, DB] = ZLayer.fromFunction(DB(_)) 88 | 89 | // 90 | 91 | class ConnectionPool(r: Ref[Vector[Connection]]): 92 | def obtain: Task[Connection] = r 93 | .modify { 94 | case h +: t => (h, t) 95 | case _ => throw new IllegalStateException("No connection available!") 96 | } 97 | .tap(c => ZIO.logInfo(s"Obtained connection: ${c.id}")) 98 | def release(c: Connection): Task[Unit] = 99 | r.modify(cs => ((), cs :+ c)).tap(_ => ZIO.logInfo(s"Released connection: ${c.id}")) 100 | def close: ZIO[Any, Nothing, Unit] = 101 | r.modify(c => (c, Vector.empty)) 102 | .flatMap(conns => ZIO.foreachDiscard(conns)(conn => ZIO.logInfo(s"Closing: $conn"))) 103 | 104 | object ConnectionPool: 105 | val live: ZLayer[Any, Nothing, ConnectionPool] = 106 | ZLayer.scoped( 107 | ZIO.acquireRelease( 108 | Ref 109 | .make(Vector(Connection("conn1"), Connection("conn2"), Connection("conn3"))) 110 | .map(ConnectionPool(_)) 111 | )(_.close) 112 | ) 113 | -------------------------------------------------------------------------------- /core/src/main/scala/Main.scala: -------------------------------------------------------------------------------- 1 | import zio.{Console, Scope, ZIO, ZIOAppDefault, ZLayer} 2 | 3 | import java.io.IOException 4 | 5 | object Main extends ZIOAppDefault: 6 | override def run: ZIO[Scope, Any, Any] = 7 | def program(api: CarApi): ZIO[Any, IOException, Unit] = for { 8 | _ <- api.register("Toyota Corolla WE98765").flatMap(Console.printLine(_)) 9 | _ <- api.register("VW Golf WN12345").flatMap(Console.printLine(_)) 10 | _ <- api.register("Tesla").flatMap(Console.printLine(_)) 11 | } yield () 12 | 13 | ZLayer 14 | .makeSome[Scope, CarApi]( 15 | CarApi.live, 16 | CarService.live, 17 | CarRepository.live, 18 | DB.live, 19 | ConnectionPool.live 20 | ) 21 | .build 22 | .map(_.get[CarApi]) 23 | .flatMap(program) 24 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | val sbtSoftwareMillVersion = "2.0.9" 2 | addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-common" % sbtSoftwareMillVersion) 3 | 4 | --------------------------------------------------------------------------------