ZIO Slick Interop
ZIO Slick Interop is a small library, that provides interop between Slick and ZIO.
Installation​
In order to use this library, we need to add the following line in our build.sbt file:
libraryDependencies += "io.scalac" %% "zio-slick-interop" % "0.4.0"
Example​
To run this example we should also add the HikariCP integration for Slick in our build.sbt file:
libraryDependencies += "com.typesafe.slick" %% "slick-hikaricp" % "3.3.3"
Here is a full working example of creating database-agnostic Slick repository:
import com.typesafe.config.ConfigFactory
import slick.interop.zio.DatabaseProvider
import slick.interop.zio.syntax._
import slick.jdbc.H2Profile.api._
import slick.jdbc.JdbcProfile
import zio.console.Console
import zio.interop.console.cats.putStrLn
import zio.{ExitCode, IO, URIO, ZEnvironment, ZIO, ZLayer}
import scala.jdk.CollectionConverters._
case class Item(id: Long, name: String)
trait ItemRepository {
  def add(name: String): IO[Throwable, Long]
  def getById(id: Long): IO[Throwable, Option[Item]]
  def upsert(name: String): IO[Throwable, Long]
}
object ItemsTable {
  class Items(tag: Tag) extends Table[Item](
    _tableTag = tag,
    _tableName = "ITEMS"
  ) {
    def id = column[Long]("ID", O.PrimaryKey, O.AutoInc)
    def name = column[String]("NAME")
    def * = (id, name) <> ((Item.apply _).tupled, Item.unapply _)
  }
  val table = TableQuery[ItemsTable.Items]
}
object SlickItemRepository {
  val live: ZLayer[DatabaseProvider, Throwable, ItemRepository] =
    ZLayer.fromServiceM { db =>
      db.profile.flatMap { profile =>
        import profile.api._
        val initialize = ZIO.fromDBIO(ItemsTable.table.schema.createIfNotExists)
        val repository = new ItemRepository {
          private val items = ItemsTable.table
          def add(name: String): IO[Throwable, Long] =
            ZIO
              .fromDBIO((items returning items.map(_.id)) += Item(0L, name))
              .provideEnvironment(ZEnvironment(db))
          def getById(id: Long): IO[Throwable, Option[Item]] = {
            val query = items.filter(_.id === id).result
            ZIO.fromDBIO(query).map(_.headOption).provideEnvironment(ZEnvironment(db))
          }
          def upsert(name: String): IO[Throwable, Long] =
            ZIO
              .fromDBIO { implicit ec =>
                (for {
                  itemOpt <- items.filter(_.name === name).result.headOption
                  id <- itemOpt.fold[DBIOAction[Long, NoStream, Effect.Write]](
                    (items returning items.map(_.id)) += Item(0L, name)
                  )(item => (items.map(_.name) update name).map(_ => item.id))
                } yield id).transactionally
              }
              .provideEnvironment(Environment(db))
        }
        initialize.as(repository).provideEnvironment(Environment(db))
      }
    }
}
object Main extends zio.App {
  private val config = ConfigFactory.parseMap(
    Map(
      "url" -> "jdbc:h2:mem:test1;DB_CLOSE_DELAY=-1",
      "driver" -> "org.h2.Driver",
      "connectionPool" -> "disabled"
    ).asJava
  )
  private val env: ZLayer[Any, Throwable, ItemRepository] =
    (ZLayer.succeed(config) ++ ZLayer.succeed[JdbcProfile](
      slick.jdbc.H2Profile
    )) >>> DatabaseProvider.live >>> SlickItemRepository.live
  val myApp: ZIO[Console with Has[ItemRepository], Throwable, Unit] =
    for {
      repo <- ZIO.service[ItemRepository]
      aId1 <- repo.add("A")
      _ <- repo.add("B")
      a <- repo.getById(1L)
      b <- repo.getById(2L)
      aId2 <- repo.upsert("A")
      _ <- putStrLn(s"$aId1 == $aId2")
      _ <- putStrLn(s"A item: $a")
      _ <- putStrLn(s"B item: $b")
    } yield ()
  override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] =
    myApp.provideCustom(env).exitCode
}