zio / zio-quill

Compile-time Language Integrated Queries for Scala

Home Page:https://zio.dev/zio-quill

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Could not find Quill::Mysql[+SnakeCase] inside ZEnvironment

hepengzheng opened this issue · comments

Hi, I'm a newbie to zio-quill and I could not bootstrap my first app with the following error:

timestamp=2023-01-15T13:48:35.216237Z level=ERROR thread=#zio-fiber-1 message="" cause="Exception in thread "zio-fiber-6,5" java.lang.Error: Defect in zio.ZEnvironment: Could not find Quill::Mysql[+SnakeCase] inside ZEnvironment(Quill$::Mysql[+SnakeCase] -> io.getquill.jdbczio.Quill$Mysql@7519998a)
	at org.example.zioserver.respository.CardRepository.layer(CardRepository.scala:43)
	at org.example.zioserver.respository.CardRepository.layer(CardRepository.scala:44)
	at org.example.zioserver.respository.CardRepository.layer(CardRepository.scala:45)
	at org.example.zioserver.Zioserver.run(Zioserver.scala:19)
	at org.example.zioserver.Zioserver.run(Zioserver.scala:19)
	at org.example.zioserver.Zioserver.run(Zioserver.scala:19)
	at org.example.zioserver.Zioserver.run(Zioserver.scala:19)
	at org.example.zioserver.Zioserver.run(Zioserver.scala:19)
	at org.example.zioserver.Zioserver.run(Zioserver.scala:19)
	at org.example.zioserver.Zioserver.run(Zioserver.scala:19)
	at org.example.zioserver.Zioserver.run(Zioserver.scala:19)"

Dependencies and version

scalaVersion = 3.2.0
sbt.version = 1.8.2

    libraryDependencies ++= Seq(
      "dev.zio" %% "zio" % "2.0.5",
      "dev.zio" %% "zio-json" % "0.4.2",
      "dev.zio" %% "zio-http" % "0.0.3",
      "dev.zio" %% "zio-logging" % "2.1.7",
      "dev.zio" %% "zio-logging-slf4j" % "2.1.7",
      "ch.qos.logback" % "logback-classic" % "1.4.5",
      "io.getquill" %% "quill-zio" % "4.6.0",
      "io.getquill" %% "quill-jdbc-zio" % "4.6.0",
      "mysql" % "mysql-connector-java" % "8.0.30",
    )

Steps to reproduce the behavior

The model layer:

import io.getquill.*
import io.getquill.jdbczio.Quill
import zio.json.*
import zio.{ZIO, ZLayer}

import java.sql.SQLException
import java.time.LocalDate
import java.util.Date

case class Card(
  id: Long,
  number: String,
  createAt: LocalDate,
  UpdateAt: LocalDate,
)

object Card {
  implicit val encoder: JsonEncoder[Card] =
    DeriveJsonEncoder.gen[Card]
}


class CardRepository(quill: Quill.Mysql[SnakeCase]) {

  import quill._

  def getCards: ZIO[Any, SQLException, List[Card]] = {
    run(query[Card])
  }
}


object CardRepository {
  def getCards: ZIO[CardRepository, SQLException, List[Card]] = {
    ZIO.serviceWithZIO[CardRepository](_.getCards)
  }

  val layer: ZLayer[Quill.Mysql[SnakeCase], Nothing, CardRepository] = {
    ZLayer {
      for {
        quill <- ZIO.service[Quill.Mysql[SnakeCase]]
      } yield new CardRepository(quill)
    }
  }
}

The http app:

import org.example.zioserver.respository.{Card, CardRepository}
import zio.*
import zio.http.*
import zio.http.model.*
import zio.json._

import java.sql.SQLException

object CardApp {
  def apply(): Http[CardService, Throwable, Request, Response] = {

    Http.collectZIO[Request] {
      case Method.GET -> !! / "cards" =>
        CardService.listAddCard.map(_.toJson).map(Response.json(_))
    }
  }
}


trait CardService {
  def listAllCard: Task[List[Card]]
}

object CardService {
  def listAddCard: ZIO[CardService, Throwable, List[Card]] = {
    ZIO.serviceWithZIO[CardService](_.listAllCard)
  }
}

case class CardServiceImpl(cardRepository: CardRepository) extends CardService {
  override def listAllCard: Task[List[Card]] = {
    cardRepository.getCards.foldCauseZIO(
      e => ZIO.logError(s"Failed to get cards $e").as(List()),
      cards => ZIO.succeed(cards)
    )
  }
}

object CardServiceImpl {
  val layer: ZLayer[CardRepository, Nothing, CardService] = {
    ZLayer {
      for {
        cardRepository <- ZIO.service[CardRepository]
      } yield CardServiceImpl(cardRepository)
    }
  }
}

The main app:

import io.getquill.*
import io.getquill.jdbczio.Quill
import org.example.zioserver.demo.card.{CardApp, CardServiceImpl}
import org.example.zioserver.demo.counter.CounterApp
import org.example.zioserver.respository.CardRepository
import zio.*
import zio.http.*
import zio.http.model.*
import zio.logging.backend.SLF4J

object Zioserver extends ZIOAppDefault {

  val run: ZIO[Any, Throwable, Nothing] = Server.serve(CardApp())
    .provide(
      Server.live,
      ZLayer.succeed(ServerConfig.default.port(8082)),
      CardServiceImpl.layer,
      CardRepository.layer,
      Quill.DataSource.fromPrefix("dbConfig"),
      Quill.Mysql.fromNamingStrategy(SnakeCase),
    )
}

How should I solve it? Thanks a lot.

@getquill/maintainers

I updated my code as such and it works:

class CardRepository(dataSource: DataSource) {
  import org.example.zioserver.QuillContext._
  def getCards: ZIO[Any, SQLException, List[Card]] = {
    run(query[Card]).provideEnvironment(ZEnvironment(dataSource))
  }
}

object CardRepository {
  def getCards: ZIO[CardRepository, SQLException, List[Card]] = {
    ZIO.serviceWithZIO[CardRepository](_.getCards)
  }
  val layer: ZLayer[DataSource, Nothing, CardRepository] =  {
    ZLayer.fromFunction(new CardRepository(_))
  }
}

the QuillContext:

object QuillContext extends MysqlZioJdbcContext(SnakeCase) {
  val dataSourceLayer: ZLayer[Any, Nothing, DataSource] =
    Quill.DataSource.fromPrefix("dbConfig").orDie
}

object Zioserver extends ZIOAppDefault {
  
  val run: ZIO[Any, Throwable, Nothing] = Server.serve(CardApp())
    .provide(
      Server.live,
      ZLayer.succeed(ServerConfig.default.port(8082)),
      CardServiceImpl.layer,
      CardRepository.layer,
      QuillContext.dataSourceLayer,
    )
}

but what's wrong with my original code?

@PengzhengHe This Quill.Mysql.fromNamingStrategy(SnakeCase) is not something you want to inject. It's some macro stuff so I guess it doesn't work when injecting at runtime and should be known at compile time. That's probably why you need to use this QuillContext object ;)

@guizmaii Hi, thanks for answering me. I wrote my original code based on the example on the quill home page: https://getquill.io/#docs

case class Person(name: String, age: Int)

class DataService(quill: Quill.Postgres[SnakeCase]) {
  import quill._
  def getPeople: ZIO[Any, SQLException, List[Person]] = run(query[Person])
}
object DataService {
  def getPeople: ZIO[DataService, SQLException, List[Person]] =
    ZIO.serviceWithZIO[DataService](_.getPeople)

  val live = ZLayer.fromFunction(new DataService(_))
}
object Main extends ZIOAppDefault {
  override def run = {
    DataService.getPeople
      .provide(
        DataService.live,
        Quill.Postgres.fromNamingStrategy(SnakeCase),
        Quill.DataSource.fromPrefix("myDatabaseConfig")
      )
      .debug("Results")
      .exitCode
  }
}

It seems like the only difference is I was using Mysql.

Interesting. We're using PG, and we're using the similar object Context ... approach you used to make things work.
We've never tried to inject it.

@guizmaii

We've never tried to inject it.

did you mean the Quill.Mysql.fromNamingStrategy(SnakeCase) thing?

I am not sure whether the code example on the home page is outdated or just wrong. But with the QuillContext object I have to write a .provideEnvironment(ZEnvironment(dataSource)) for every sql it's tedious. ;)

did you mean the Quill.Mysql.fromNamingStrategy(SnakeCase) thing?

Yes

Thanks.