Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zio #1989

Merged
merged 9 commits into from Mar 14, 2021
Merged

Zio #1989

merged 9 commits into from Mar 14, 2021

Conversation

deusaquilus
Copy link
Collaborator

@deusaquilus deusaquilus commented Sep 29, 2020

Okay, this has been a while in the making but we seem to be done. Quill ZIO effects how have the following signature:

val result: ZIO[BlockingConnection, Throwable, Person] = run(query[Person])
// This is a shorthand for:
val result: ZIO[Has[Connection] with Has[Blocking.Service], Throwable, Person] = run(query[Person])
// a.k.a
val result: ZIO[Has[Connection] with Blocking, Throwable, Person] = run(query[Person])

Unlike many other contexts, the ZIO Quill contexts do not require connections or data-sources to be passed inside. Since they return a ZIO that encapsulates the dependency, they can be completely static:

object MyZioPostgresContext extends PostgresZioJdbcContext(Literal)

I have provided various helper functions in order in order to make this process easier, these are in ZioJdbc.scala
For an example, a simple ZIO app using JDBC goes as follows:

  object MyPostgresContext extends PostgresZioJdbcContext(Literal)
  import MyPostgresContext._

   val zioConn =
    ZLayer.fromManaged(for {
      ds <- ZManaged.fromAutoCloseable(Task(JdbcContextConfig(LoadConfig("testPostgresDB")).dataSource))
      conn <- ZManaged.fromAutoCloseable(Task(ds.getConnection))
    } yield conn)

  override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = {
    val people = quote {
      query[Person].filter(p => p.name == "Alex")
    }
    MyPostgresContext.run(people)
      .tap(result => putStrLn(result.toString))
      .provideCustomLayer(zioConn).exitCode
  }

The zioConn part can be significantly simplified by using Layers in ZioJdbc.scala.

    val zioConn =
    Layers.dataSourceFromPrefix("testPostgresDB") >>> Layers.dataSourceToConnection

One important thing to note is that for the streaming methods (i.e. context.stream which returns a ZStream), an option to chunk the stream itself is provided. This is based on a method called chunkedFetch which is based on ZStream's fromIterator with the significant difference that a single effect is not a single row but a chunk instead. The actual chunking logic is done by guardedChunkFill. Hopefully future releases of ZStream will incorporate this functionality so it will not be necessary to maintain here.

Also, it is worthwhile to note that the context.prepare functions also now have a zio idiomatic signature:

val prepareInsert: ZIO[BlockingConnection, Throwable, PreparedStatement] = prepare(query[Product].insert(lift(productEntries.head)))

These methods are allow a user to get down into the JDBC layers upon executing a query if this is needed.


def transaction[A](f: Task[A]): Task[A] = {
val dbEffects = for {
connectionRef <- currentConnection
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about ZIO.environment[Connection] instead of FiberRef?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adamgfraser suggested that too. Is we’re going in that direction, should run return a task with a Connection environment as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, if we’re gonna do that, should we just rely on the user doing their own bracketing of the connection?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And also, should we take environment of PreparedStatement in the cases that we use it. Or if we’re managing it ourselves should we bracket it?


override protected def withConnection[T](f: Connection => Task[T]): Task[T] =
for {
maybeConnectionRef <- currentConnection
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should it be wrapped in effectBlocking?

Comment on lines 80 to 83
// TODO Are we really catching the result of the conn.rollback() Task's exception?
catchAll(Task(conn.rollback()) *> Task.halt(cause))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of catching rollback errors, maybe one possible alternative could be to combine the causes (like with ++ for example) since Cause can represent multiple failures.

@reibitto
Copy link

I'd also be willing to help with this, but I don't want to get in the way. Maybe like upgrading to ZIO 1.0.3 later and fixing the breaking changes (though you might not run into too many).

There's nitpicky stuff like "you can use shorter combinators like ZManaged.fromEffect" here and there, but I think minor stuff like that can wait until a final pass at the end.

@emarx
Copy link

emarx commented Oct 28, 2020

This is so exciting!

@deusaquilus
Copy link
Collaborator Author

deusaquilus commented Nov 1, 2020

@reibitto Any help is highly appreciated, if you'd like to work on this with me or take over please let me know. Also, I'm not sure about blocking. Right now the effect type is RIO[Connection, T], if we change to blocking it's RIO[Connection with Blocking, T]. Does that make usage simpler or more difficult? (Also, I just updated to ZIO 1.0.3)

@reibitto
Copy link

reibitto commented Nov 1, 2020

The Blocking part is a bit tricky and I'm not sure what the "best practice" is here. I mean, it makes sense to use Blocking, but how best to expose it I'm not 100% sure. Generally provideSome and so on shouldn't be called often as it's usually only used when wiring the app together. If @adamgfraser suggested just ZIO.environment[Connection] then he might have other ideas that I'm not thinking of.

Whether it makes sense to create a Database / Quill ZIO service here is another thing to think about maybe?

For reference, there's tranzactio which uses Has[JdbcConnection] with Blocking:
https://github.com/gaelrenoux/tranzactio/blob/7e5c58452a9ad85bc6d0f6c72487559a17b9a672/src/main/scala/io/github/gaelrenoux/tranzactio/anorm/package.scala#L12

Besides the questions above, what are the remaining parts? I see tests commented out, but is there anything else? If the main ZIO modules are pretty much done I can try giving it a spin in my app and seeing what happens. If that works fine, I can maybe try exploring the other ideas I mentioned above.


// Primary method used to actually run Quill context commands query, insert, update, delete and others
override protected def withConnectionWrapped[T](f: Connection => T): RIO[BlockingConnection, T] =
blocking(RIO.fromFunction((conn: BlockingConnection) => f(conn.get)))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

f(conn.get) cloud throw exception. Would be better wrapper that with some effect

case Success(_) =>
UIO(conn.get.commit())
case Failure(cause) =>
UIO(conn.get.rollback()).foldCauseM(
Copy link
Collaborator

@jilen jilen Mar 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UIO.apply expect an unexceptional effect. Those jdbc calls will throw SQLException

@deusaquilus deusaquilus merged commit 5ac2b43 into master Mar 14, 2021
@deusaquilus deusaquilus deleted the zio branch July 11, 2021 07:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants