Skip to content

Commit 4e3d021

Browse files
committed
Merge branch 'master' of github.com:jdegoes/functional-scala
2 parents c64d5b6 + bcbf211 commit 4e3d021

File tree

13 files changed

+195
-90
lines changed

13 files changed

+195
-90
lines changed

build.sbt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ val DoobieVersion = "0.7.0-M5"
4848
val ZIOVersion = "1.0-RC4"
4949
val PureConfigVersion = "0.11.0"
5050
val H2Version = "1.4.199"
51-
val FlywayVersion = "6.0.0-beta2"
5251

5352
libraryDependencies ++= Seq(
5453
// -- testing --
@@ -62,25 +61,20 @@ libraryDependencies ++= Seq(
6261
"org.scalaz" %% "scalaz-zio-interop-cats" % ZIOVersion,
6362
// Http4s
6463
"org.http4s" %% "http4s-blaze-server" % Http4sVersion,
65-
"org.http4s" %% "http4s-blaze-client" % Http4sVersion,
6664
"org.http4s" %% "http4s-circe" % Http4sVersion,
6765
"org.http4s" %% "http4s-dsl" % Http4sVersion,
6866
// Circe
6967
"io.circe" %% "circe-generic" % CirceVersion,
7068
"io.circe" %% "circe-generic-extras" % CirceVersion,
7169
// Doobie
7270
"org.tpolecat" %% "doobie-core" % DoobieVersion,
73-
"org.tpolecat" %% "doobie-h2" % DoobieVersion,
74-
"org.tpolecat" %% "doobie-hikari" % DoobieVersion,
7571
"org.tpolecat" %% "doobie-h2" % DoobieVersion,
7672
// log4j
7773
"org.slf4j" % "slf4j-log4j12" % "1.7.26",
7874
//pure config
7975
"com.github.pureconfig" %% "pureconfig" % PureConfigVersion,
8076
//h2
8177
"com.h2database" % "h2" % H2Version,
82-
//flyway
83-
"org.flywaydb" % "flyway-core" % FlywayVersion,
8478
// Ammonite
8579
"com.lihaoyi" % "ammonite" % "1.1.2" % "test" cross CrossVersion.full
8680
)

src/main/resources/application.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ api {
44
}
55

66
db-config {
7-
url = "jdbc:h2:~/test;DB_CLOSE_DELAY=-1"
7+
url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"
88
user = ""
99
password = ""
1010
}

src/main/scala/net/degoes/06-application/Data.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ package net.degoes.applications.data
22

33
final case class User(id: Long, name: String)
44

5-
final case class UserNotFound(id: Int) extends Exception
5+
final case class UserNotFound(id: Int) extends Exception

src/main/scala/net/degoes/06-application/Main.scala

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
/**
2+
* This example is inspired by [[https://github.com/mschuwalow/zio-todo-backend]]
3+
*/
14
package net.degoes.applications
25

36
import cats.effect.ExitCode
7+
import net.degoes.applications.configuration.Configuration
48
import net.degoes.applications.db.Persistence
59
import net.degoes.applications.http.Api
6-
import net.degoes.applications.configuration._
710
import org.http4s.implicits._
811
import org.http4s.server.Router
912
import org.http4s.server.blaze.BlazeServerBuilder
@@ -16,19 +19,16 @@ import scalaz.zio.{ Task, TaskR, ZIO, _ }
1619

1720
object Main extends App {
1821

19-
type AppEnvironment = Clock with Blocking with Persistence
22+
type AppEnvironment = Clock with Persistence
2023

2124
type AppTask[A] = TaskR[AppEnvironment, A]
2225

2326
override def run(args: List[String]): ZIO[Environment, Nothing, Int] = {
2427
val program: ZIO[Main.Environment, Throwable, Unit] = for {
25-
conf <- configuration.loadConfig
26-
_ <- configuration.initDB(conf.dbConfig)
28+
conf <- configuration.load.provide(Configuration.Live)
29+
blockingEC <- blocking.blockingExecutor.map(_.asEC).provide(Blocking.Live)
2730

28-
blockingEnv <- ZIO.environment[Blocking]
29-
blockingEC <- blockingEnv.blocking.blockingExecutor.map(_.asEC)
30-
31-
transactorR = configuration.mkTransactor(
31+
transactorR = Persistence.mkTransactor(
3232
conf.dbConfig,
3333
Platform.executor.asEC,
3434
blockingEC
@@ -39,16 +39,17 @@ object Main extends App {
3939
).orNotFound
4040

4141
server = ZIO.runtime[AppEnvironment].flatMap { implicit rts =>
42-
BlazeServerBuilder[AppTask]
43-
.bindHttp(conf.api.port, "0.0.0.0")
44-
.withHttpApp(CORS(httpApp))
45-
.serve
46-
.compile[AppTask, AppTask, ExitCode]
47-
.drain
42+
db.createTable *>
43+
BlazeServerBuilder[AppTask]
44+
.bindHttp(conf.api.port, "0.0.0.0")
45+
.withHttpApp(CORS(httpApp))
46+
.serve
47+
.compile[AppTask, AppTask, ExitCode]
48+
.drain
4849
}
4950
program <- transactorR.use { transactor =>
5051
server.provideSome[Environment] { _ =>
51-
new Clock.Live with Blocking.Live with Persistence.Live {
52+
new Clock.Live with Persistence.Live {
5253
override protected def tnx: doobie.Transactor[Task] = transactor
5354
}
5455
}

src/main/scala/net/degoes/06-application/configuration/config.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
package net.degoes.applications.configuration
2+
import pureconfig.loadConfigOrThrow
3+
import scalaz.zio.{ Task, TaskR }
4+
import pureconfig.generic.auto._
25

36
case class Config(api: ApiConfig, dbConfig: DbConfig)
47
case class ApiConfig(endpoint: String, port: Int)
@@ -7,3 +10,22 @@ case class DbConfig(
710
user: String,
811
password: String
912
)
13+
14+
trait Configuration extends Serializable {
15+
val config: Configuration.Service[Any]
16+
}
17+
18+
object Configuration {
19+
trait Service[R] {
20+
val load: TaskR[R, Config]
21+
}
22+
23+
trait Live extends Configuration {
24+
val config: Service[Any] = new Service[Any] {
25+
val load: Task[Config] = Task.effect(loadConfigOrThrow[Config])
26+
}
27+
}
28+
29+
30+
object Live extends Live
31+
}
Lines changed: 4 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,7 @@
11
package net.degoes.applications
22

3-
import doobie.h2.H2Transactor
4-
import org.flywaydb.core.Flyway
5-
import pureconfig.loadConfigOrThrow
6-
import scala.concurrent.ExecutionContext
7-
import scalaz.zio.{ Managed, Reservation, Task, ZIO }
8-
import pureconfig.generic.auto._
3+
import scalaz.zio.TaskR
94

10-
package object configuration {
11-
def loadConfig: Task[Config] = Task.effect(loadConfigOrThrow[Config])
12-
def initDB(conf: DbConfig): Task[Unit] =
13-
Task.effect {
14-
Flyway
15-
.configure()
16-
.dataSource(conf.url, conf.user, conf.password)
17-
.load()
18-
.migrate()
19-
}.unit
20-
21-
def mkTransactor(
22-
conf: DbConfig,
23-
connectEC: ExecutionContext,
24-
transactEC: ExecutionContext
25-
): Managed[Throwable, H2Transactor[Task]] = {
26-
import scalaz.zio.interop.catz._
27-
28-
val xa = H2Transactor
29-
.newH2Transactor[Task](conf.url, conf.user, conf.password, connectEC, transactEC)
30-
31-
val res = xa.allocated.map {
32-
case (transactor, cleanupM) =>
33-
Reservation(ZIO.succeed(transactor), cleanupM.orDie)
34-
}.uninterruptible
35-
36-
Managed(res)
37-
}
38-
}
5+
package object configuration extends Configuration.Service[Configuration] {
6+
val load: TaskR[Configuration, Config] = TaskR.accessM(_.config.load)
7+
}

src/main/scala/net/degoes/06-application/db/Persistence.scala

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package net.degoes.applications.db
22

3-
import net.degoes.applications.data.{ User, UserNotFound }
3+
import doobie.h2.H2Transactor
4+
import doobie.implicits._
45
import doobie.{ Query0, Transactor, Update0 }
6+
import net.degoes.applications.configuration.DbConfig
7+
import net.degoes.applications.data.{ User, UserNotFound }
8+
import scala.concurrent.ExecutionContext
59
import scalaz.zio._
6-
import doobie.implicits._
7-
import scalaz.zio.interop.catz._
10+
import scalaz.zio.interop.catz.taskConcurrentInstances
811

912
/**
1013
* Persistence Service
@@ -14,7 +17,9 @@ trait Persistence extends Serializable {
1417
}
1518

1619
object Persistence {
20+
1721
trait Service[R] {
22+
val createTable: TaskR[R, Unit]
1823
def get(id: Int): TaskR[R, User]
1924
def create(user: User): TaskR[R, User]
2025
def delete(id: Int): TaskR[R, Unit]
@@ -29,13 +34,16 @@ object Persistence {
2934

3035
val userPersistence: Service[Any] = new Service[Any] {
3136

37+
val createTable: Task[Unit] =
38+
SQL.createTable.run.transact(tnx).foldM(err => Task.fail(err), _ => Task.succeed(()))
39+
3240
def get(id: Int): Task[User] =
3341
SQL
3442
.get(id)
3543
.option
3644
.transact(tnx)
3745
.foldM(
38-
err => Task.fail(err),
46+
Task.fail,
3947
maybeUser => Task.require(UserNotFound(id))(Task.succeed(maybeUser))
4048
)
4149

@@ -51,20 +59,47 @@ object Persistence {
5159
.delete(id)
5260
.run
5361
.transact(tnx)
54-
.foldM(err => Task.fail(err), _ => Task.succeed(()))
62+
.unit
63+
.orDie
5564
}
5665

5766
object SQL {
5867

68+
def createTable: Update0 = sql"""CREATE TABLE IF NOT EXISTS Users (id int PRIMARY KEY, name varchar)""".update
69+
5970
def get(id: Int): Query0[User] =
6071
sql"""SELECT * FROM USERS WHERE ID = $id """.query[User]
6172

6273
def create(user: User): Update0 =
63-
sql"""INSERT INTO USERS (id, name) VALUES (${user.id}, ${user.name})""".update
74+
sql"""INSERT INTO USERS (ID, NAME) VALUES (${user.id}, ${user.name})""".update
6475

6576
def delete(id: Int): Update0 =
66-
sql"""DELETE FROM USERS WHERE id = $id""".update
77+
sql"""DELETE FROM USERS WHERE ID = $id""".update
6778
}
79+
6880
}
81+
def mkTransactor(
82+
conf: DbConfig,
83+
connectEC: ExecutionContext,
84+
transactEC: ExecutionContext
85+
): Managed[Throwable, H2Transactor[Task]] = {
86+
import scalaz.zio.interop.catz._
6987

88+
val xa = H2Transactor
89+
.newH2Transactor[Task](conf.url, conf.user, conf.password, connectEC, transactEC)
90+
91+
val res = xa.allocated.map {
92+
case (transactor, cleanupM) =>
93+
Reservation(ZIO.succeed(transactor), cleanupM.orDie)
94+
}.uninterruptible
95+
96+
Managed(res)
97+
}
98+
99+
/**
100+
* Persistence Module for test
101+
*/
102+
case class Test(users: Ref[Vector[User]]) extends Persistence {
103+
override val userPersistence: Service[Any] = ???
104+
}
70105
}

src/main/scala/net/degoes/06-application/db/package.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import scalaz.zio.{ TaskR, ZIO }
77
* Helper that will access to the Persistence Service
88
*/
99
package object db extends Persistence.Service[Persistence] {
10-
10+
val createTable: TaskR[Persistence, Unit] = ZIO.accessM(_.userPersistence.createTable)
1111
def get(id: Int): TaskR[Persistence, User] = ZIO.accessM(_.userPersistence.get(id))
1212
def create(user: User): TaskR[Persistence, User] = ZIO.accessM(_.userPersistence.create(user))
1313
def delete(id: Int): TaskR[Persistence, Unit] = ZIO.accessM(_.userPersistence.delete(id))

src/main/scala/net/degoes/06-application/http/Api.scala

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package net.degoes.applications.http
22

3+
import io.circe.generic.auto._
4+
import io.circe.{Decoder, Encoder}
35
import net.degoes.applications.data.User
4-
import net.degoes.applications.db._
5-
import net.degoes.applications.db.Persistence
6-
import io.circe.{ Decoder, Encoder }
7-
import org.http4s.{ EntityDecoder, EntityEncoder, HttpRoutes }
6+
import net.degoes.applications.db.{Persistence, _}
7+
import org.http4s.circe._
88
import org.http4s.dsl.Http4sDsl
9+
import org.http4s.{EntityDecoder, EntityEncoder, HttpRoutes}
910
import scalaz.zio._
10-
import org.http4s.circe._
1111
import scalaz.zio.interop.catz._
12-
import io.circe.generic.auto._
1312

1413
final case class Api[R <: Persistence](rootUri: String) {
1514

@@ -30,7 +29,7 @@ final case class Api[R <: Persistence](rootUri: String) {
3029
Created(create(user))
3130
}
3231
case DELETE -> Root / IntVar(id) =>
33-
delete(id).foldM(_ => NotFound(), Ok(_))
32+
(get(id) *> delete(id)).foldM(_ => NotFound(), Ok(_))
3433
}
3534

3635
}

src/test/scala/06-application/TestRuntime.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package example.db
1+
package net.degoes.applications
22

33
import java.util.{Timer, TimerTask}
44
import org.specs2.Specification
@@ -32,7 +32,7 @@ abstract class TestRuntime extends Specification with DefaultRuntime {
3232
}
3333
timer.schedule(task, timeout.toMillis)
3434

35-
unsafeRunToFuture(zio.sandbox.mapError(FiberFailure).map(p.success))
35+
unsafeRunToFuture(zio.sandbox.mapError(FiberFailure).map(p.success).supervised)
3636
p.future
3737
}
3838

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package net.degoes.applications.configuration
2+
import net.degoes.applications.TestRuntime
3+
4+
final class ConfigurationSpec extends TestRuntime {
5+
6+
def is =
7+
s2"""
8+
Configuration.Live should work correctly $e
9+
"""
10+
11+
def e =
12+
load.provide(Configuration.Live).map { config =>
13+
config.api must_=== ApiConfig("127.0.0.1", 8080)
14+
config.dbConfig must_=== DbConfig("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", "", "")
15+
}
16+
17+
}

0 commit comments

Comments
 (0)