Skip to content

Commit

Permalink
migrated to ZIO 2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
sviezypan committed Feb 7, 2022
1 parent e64fb08 commit 2fe7fa3
Show file tree
Hide file tree
Showing 18 changed files with 350 additions and 340 deletions.
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,4 @@ Other than ZIO-SQL I am using zio-http, zio-config, zio-json and other libraries
```

## TODO
1. upgrade to ZIO 2.0
2. add `transactions` example - order references customer through foreign key, check if customer exist before inserting
3. introduce service layer between api and repo when working on point 2.
1. add `transactions` example
27 changes: 9 additions & 18 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
//depending on snapshot as zio-sql is not yet released
val zioSqlVersion = "0.0.0+965-8f6871e5-SNAPSHOT"
val zioSqlVersion = "0.0.0+1004-0bda0210+20220207-1412-SNAPSHOT"

val zioVersion = "2.0.0-RC2"
val zioHttpVersion = "2.0.0-RC2"
val zioJsonVersion = "0.3.0-RC3"
val zioConfigVersion = "3.0.0-RC2"

val zioVersion = "1.0.12"
val zioHttpVersion = "1.0.0.0-RC17"
val zioJsonVersion = "0.2.0-M3"
val zioLoggingVersion = "0.5.14"
val logbackVersion = "1.2.7"
val testcontainersVersion = "1.16.2"
val testcontainersScalaVersion = "0.39.12"
val zioConfigVersion = "1.0.10"
val zioMagicVersion = "0.3.11"
val zioSchemaVersion = "0.1.4"

lazy val root = (project in file("."))
.settings(
Expand All @@ -22,27 +19,21 @@ lazy val root = (project in file("."))
scalaVersion := "2.13.7"
)
),
resolvers +=
"Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots",
// resolvers +=
// "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots",
name := "zio-sql-example",
libraryDependencies ++= Seq(
//core
"dev.zio" %% "zio" % zioVersion,
"io.github.kitlangton" %% "zio-magic" % zioMagicVersion,
//sql
"dev.zio" %% "zio-sql-postgres" % zioSqlVersion,
//http
"io.d11" %% "zhttp" % zioHttpVersion,
"io.d11" %% "zhttp-test" % zioHttpVersion % Test,
//config
"dev.zio" %% "zio-config" % zioConfigVersion,
"dev.zio" %% "zio-config-typesafe" % zioConfigVersion,
"dev.zio" %% "zio-config-magnolia" % zioConfigVersion,
"dev.zio" %% "zio-schema" % zioSchemaVersion,
"dev.zio" %% "zio-schema-derivation" % zioSchemaVersion,
//logging
"dev.zio" %% "zio-logging" % zioLoggingVersion,
"dev.zio" %% "zio-logging-slf4j" % zioLoggingVersion,
"ch.qos.logback" % "logback-classic" % logbackVersion,
//json
"dev.zio" %% "zio-json" % zioJsonVersion,
// test dependencies
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ services:
- POSTGRES_PASSWORD=password
- POSTGRES_DB=zio-sql-example-db
ports:
- "5432:5432"
- "5435:5432"
1 change: 1 addition & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.5")
addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.7")
addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.1.20")
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.34")
4 changes: 2 additions & 2 deletions src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ server-config {

db-config {
host = "localhost"
port = "5432"
port = "5435"
dbName = "zio-sql-example-db"
url = "jdbc:postgresql://localhost:5432/zio-sql-example-db?reWriteBatchedInserts=true"
url = "jdbc:postgresql://localhost:5435/zio-sql-example-db?reWriteBatchedInserts=true"
user = "postgres"
password = "password"
driver = "org.postgresql.Driver"
Expand Down
29 changes: 11 additions & 18 deletions src/main/scala/sviezypan/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,29 @@ import zhttp.service.server.ServerChannelFactory
import zhttp.service.{EventLoopGroup, Server}
import zio._
import zio.config._
import zio.logging._
import sviezypan.config.configuration._
import sviezypan.api.HttpRoutes
import sviezypan.healthcheck.Healthcheck
import sviezypan.repo.{CustomerRepositoryLive, OrderRepositoryLive}
import zio.magic._
import zio.sql.ConnectionPool

object Main extends App {
object Main extends ZIOAppDefault {

val loggingEnv =
Logging.console(LogLevel.Info, LogFormat.ColoredLogFormat()) >>>
Logging.withRootLoggerName("zio-sql-example")

def run(args: List[String]): URIO[ZEnv, ExitCode] =
def run =
getConfig[ServerConfig]
.map(config => Server.port(config.port) ++
Server.app(
HttpRoutes.app +++
Healthcheck.expose
))
.flatMap(_.make.useForever)
.injectCustom(
.map(config =>
Server.port(config.port) ++
Server.app(
HttpRoutes.app ++
Healthcheck.expose
)
)
.flatMap(_.start)
.provide(
ServerConfig.layer,
ServerChannelFactory.auto,
EventLoopGroup.auto(),
OrderRepositoryLive.layer,
CustomerRepositoryLive.layer,
loggingEnv,
ConnectionPool.live,
DbConfig.layer,
DbConfig.connectionPoolConfig
)
Expand Down
115 changes: 44 additions & 71 deletions src/main/scala/sviezypan/api/HttpRoutes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,139 +2,112 @@ package sviezypan.api

import zhttp.http._
import zio._
import zio.stream._
import zio.json._
import zio.logging._
import java.util.UUID
import sviezypan.domain._
import sviezypan.repo._

object HttpRoutes {

val app
: HttpApp[Has[OrderRepository] with Has[CustomerRepository] with Logging, Throwable] =
HttpApp.collectM {
case Method.GET -> Root / "orders" / "count" =>
val app: HttpApp[OrderRepository with CustomerRepository, Throwable] =
Http.collectZIO {
case Method.GET -> !! / "orders" / "count" =>
OrderRepository
.countAllOrders()
.either
.map {
case Right(count) => Response.jsonString(s"{\"count\": \"${count.toString()}\"}")
case Left(_) => Response.status(Status.INTERNAL_SERVER_ERROR)
case Right(count) =>
Response.json(s"{\"count\": \"${count.toString()}\"}")
case Left(_) => Response.status(Status.INTERNAL_SERVER_ERROR)
}

case Method.GET -> Root / "customers" / "orders" / "join" =>
case Method.GET -> !! / "customers" / "orders" / "join" =>
OrderRepository
.findAllWithNames()
.runCollect
.map(chunk => CustomerWrapper(chunk.toList))
.either
.map {
case Right(customers) => Response.jsonString(customers.toJson)
case Left(_) => Response.status(Status.INTERNAL_SERVER_ERROR)
case Right(customers) => Response.json(customers.toJson)
case Left(_) => Response.status(Status.INTERNAL_SERVER_ERROR)
}

case Method.GET -> Root / "customers" / "orders" / "latest-date" =>
case Method.GET -> !! / "customers" / "orders" / "latest-date" =>
CustomerRepository
.findAllWithLatestOrder()
.runCollect
.map(chunk => CustomerWrapper(chunk.toList))
.either
.map {
case Right(customers) => Response.jsonString(customers.toJson)
case Left(_) => Response.status(Status.INTERNAL_SERVER_ERROR)
case Right(customers) => Response.json(customers.toJson)
case Left(_) => Response.status(Status.INTERNAL_SERVER_ERROR)
}

case Method.GET -> Root / "customers" / "orders" / "count" =>
case Method.GET -> !! / "customers" / "orders" / "count" =>
CustomerRepository
.findAllWithCountOfOrders()
.runCollect
.map(chunk => CustomerCountWrapper(chunk.toList))
.either
.map {
case Right(customers) => Response.jsonString(customers.toJson)
case Left(_) => Response.status(Status.INTERNAL_SERVER_ERROR)
case Right(customers) => Response.json(customers.toJson)
case Left(_) => Response.status(Status.INTERNAL_SERVER_ERROR)
}

case Method.GET -> Root / "customers" =>
//TODO what is the right way to stream response to xzio-http ???
ZIO.succeed(
Response.HttpResponse(
status = Status.OK,
headers = List(Header.contentTypeJson),
content = HttpData.fromStream(
CustomerRepository
.findAll()
.flatMap(customer =>
Stream.fromChunk(Chunk.fromArray(customer.toJson.getBytes(HTTP_CHARSET)))
)
),
)
)
case Method.GET -> !! / "customers" =>
CustomerRepository
.findAll()
.runCollect
.map(ch => Response.json(ch.toJson))


case Method.GET -> Root / "orders" =>
//TODO what is the right way to stream response to xzio-http ???
ZIO.succeed(
Response.HttpResponse(
status = Status.OK,
headers = List(Header.contentTypeJson),
content = HttpData.fromStream(
OrderRepository
.findAll()
.flatMap(order =>
Stream.fromChunk(Chunk.fromArray(order.toJson.getBytes(HTTP_CHARSET)))
)
),
)
)
case Method.GET -> !! / "orders" =>
OrderRepository
.findAll()
.runCollect
.map(ch => Response.json(ch.toJson))

case req @ Method.POST -> Root / "customers" =>
case req @ Method.POST -> !! / "customers" =>
(for {
body <- ZIO
.fromOption(req.getBodyAsString)
body <- req.getBodyAsString
.flatMap(request => ZIO.fromEither(request.fromJson[Customer]))
.tapError(_ => log.info(s"Unparseable body ${req.getBodyAsString}"))
.tapError(_ =>
ZIO.logInfo(s"Unparseable body ${req.getBodyAsString}")
)
_ <- CustomerRepository.create(body)
} yield ()).either.map {
case Right(_) => Response.ok
case Left(_) => Response.status(Status.BAD_REQUEST)
}

case Method.GET -> Root / "customers" / id =>
parseUUID(id)
.flatMap(id => CustomerRepository.findById(id))
case Method.GET -> !! / "customers" / zhttp.http.uuid(id) =>
CustomerRepository
.findById(id)
.either
.map {
case Right(customer) => Response.jsonString(customer.toJson)
case Right(customer) => Response.json(customer.toJson)
case Left(e) => Response.text(e.getMessage())
}

case req @ Method.POST -> Root / "orders" =>
case req @ Method.POST -> !! / "orders" =>
(for {
body <- ZIO
.fromOption(req.getBodyAsString)
body <- req.getBodyAsString
.flatMap(request => ZIO.fromEither(request.fromJson[Order]))
.tapError(_ => log.info(s"Unparseable body ${req.getBodyAsString}"))
.tapError(_ =>
ZIO.logInfo(s"Unparseable body ${req.getBodyAsString}")
)
_ <- OrderRepository.add(body)
} yield ()).either.map {
case Right(_) => Response.ok
case Left(_) => Response.status(Status.BAD_REQUEST)
}

case Method.GET -> Root / "orders" / id =>
parseUUID(id)
.flatMap(id => OrderRepository.findOrderById(id))
case Method.GET -> !! / "orders" / zhttp.http.uuid(id) =>
OrderRepository
.findOrderById(id)
.either
.map {
case Right(order) => Response.jsonString(order.toJson)
case Right(order) => Response.json(order.toJson)
case Left(e) => Response.text(e.getMessage())
}

}

private def parseUUID(id: String): IO[DomainError.ValidationError, UUID] =
ZIO
.fromTry(scala.util.Try(UUID.fromString(id)))
.mapError(_ => DomainError.ValidationError(s"$id is not a valid UUID"))

}
12 changes: 5 additions & 7 deletions src/main/scala/sviezypan/config/configuration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@ object configuration {
int("port").default(8090)
}.to[ServerConfig]

val layer = IO
.fromEither(TypesafeConfigSource.fromTypesafeConfig(ConfigFactory.defaultApplication()))
val layer = ZIO.attempt(TypesafeConfigSource.fromTypesafeConfig(ZIO.attempt(ConfigFactory.defaultApplication())))
.map(source => serverConfigDescription from source)
.flatMap(config => ZIO.fromEither(read(config)))
.flatMap(config => read(config))
.mapError(e => DomainError.ConfigError(e))
.toLayer
}
Expand All @@ -43,16 +42,15 @@ object configuration {

val dbConfigDescriptor = nested("db-config")(descriptor[DbConfig])

val layer = IO
.fromEither(TypesafeConfigSource.fromTypesafeConfig(ConfigFactory.defaultApplication()))
val layer = ZIO.attempt(TypesafeConfigSource.fromTypesafeConfig(ZIO.attempt(ConfigFactory.defaultApplication())))
.map(source => dbConfigDescriptor from source)
.flatMap(config => ZIO.fromEither(read(config)))
.flatMap(config => read(config))
.mapError(e =>
DomainError.ConfigError(e)
)
.toLayer

val connectionPoolConfig: ZLayer[Has[DbConfig], Throwable, Has[ConnectionPoolConfig]] =
val connectionPoolConfig: ZLayer[DbConfig, Throwable, ConnectionPoolConfig] =
(for {
serverConfig <- ZIO.service[DbConfig]
} yield (ConnectionPoolConfig(
Expand Down
Loading

0 comments on commit 2fe7fa3

Please sign in to comment.