riskidentdms / dbzio   1.0.2

GitHub

Monadic bridge between DBIO & ZIO

Scala versions: 2.13 2.12

DBZIO

DBZIO is a wrapper to combine ZIO and DBIO actions in one for-comprehension. Unlike other libraries, DBZIO provides possibility to run the resulting action in a context of database transaction.

Build & Tests dbzio Scala version support

Prerequisites

How to use the library

Add dependency to the project

libraryDependencies += "com.riskident" %% "dbzio" % <Version>

In the code:

import com.riskident.dbzio._
import slick.jdbc.<SomeProfile>.api._
import zio._

...
// Read data from DB
val readFromDb: DBIO[Vector[Data]] = sql"SELECT * FROM table".as[Data]
// Get relevant data from a remote service
val transformData: Vector[Data] => Task[List[OtherData]] = ...
// Save updated data in DB
val saveDataInDb: List[OtherData] => DBIO[Int] = data => sqlu"INSERT INTO table2 VALUES ($data)"

// The combined action
val action = for {
  data <- DBZIO(readFromDb)
  newData <- DBZIO(transformData(data))
  res <- DBZIO(saveDataInDb(newData))
} yield res

// The overall action should run within a db-transaction
val dbzioAction: DBZIO[Any, Int] = action.transactionally

// Back to ZIO world
val zioAction: RIO[DbDependency, Int] = dbzioAction.result

In the example above:

  1. some data is read from DB
  2. this data is sent to some remote service, which returns a new data relevant to the one being sent
  3. received data is saved in the DB

The whole operation is done inside db-transaction

Using DBZIO in tests

DBZIO provides a way to easily provide DbDependency for tests. In order to achieve it, add a dependency to the project

libraryDependencies += "com.riskident" %% "dbzio-test" % <Version> % Test

And in tests add the following:

import com.riskident.dbzio.{Db, DbDependency, TestLayers}
import com.typesafe.config.ConfigFactory
import slick.jdbc.H2Profile.api.{Database => _}
import slick.jdbc.JdbcBackend.Database
import zio.test._
import zio.{Task, ZIO, ZLayer}

object Example1 extends ZIOSpecDefault with TestLayers {
  override type Config = com.typesafe.config.Config

  override def produceConfig(string: String): Task[Config] = ZIO.attempt {
    ConfigFactory
      .parseString(string)
      .resolve()
  }

  override def makeDb(config: Config): Task[Database] =
    ZIO.attempt(Db.forConfig(path = "db", config = config, classLoader = this.getClass.getClassLoader))

  val testLayer: ZLayer[TestEnvironment, TestFailure[Throwable], DbDependency] = testDbLayer.mapError(TestFailure.fail)

  override def spec: Spec[TestEnvironment, Any] =
    suite("Some tests using db")(...).provideLayer(testLayer)
}

This will allow to use H2 in-memory database with random name in each test, effectively running tests in parallel with separate databases.