Skip to content

Instantly share code, notes, and snippets.

@felher
Last active August 9, 2019 11:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save felher/412cb1a994f2825aeefe33db598aaa2e to your computer and use it in GitHub Desktop.
Save felher/412cb1a994f2825aeefe33db598aaa2e to your computer and use it in GitHub Desktop.

Hey folks. I'm a bit unsure about how to handle (doobie) database transactions in a FT style. Say I have a three small algebras and something using them

trait Users[F[_]] { ... }
trait Groups[F[_]] { ... }
trait Mails[F[_]] { ... }

def use[F[_]](implicit U: Users[F], G: Groups[F], M: Mails[F]) = for {
  _ <- U.doSomething
  _ <- G.doSomething
  _ <- M.doSomething
} yield ()

Users and Groups do something with a database while Mail sends mails. Currently, all of their implementations are in IO and at some point I will call use[IO] and run that.

That means that Users.doSomething will take a doobie ConnectionIO and transact it to get an IO. This works fine, but how do I handle the case where doSomething of Users and doSomething of Groups should happen within the same transaction? There are a few solutions I can think of:

Solution 1

I could change all the implementations to use ConnectionIO, using LiftIO to do mail sending. Somewhere in my program I can then transact and run the result of use[ConnectionIO].

Provided M.doSomething is not too time consuming, is there something wrong with changing every implementation from IO to ConnectionIO?

Solution 2

I could provide a new algebra trait Transactor[F[_]] { def trans[A](c: ConnectionIO[A]): F[A] }, implement Users and Groups in Terms of ConnectionIO and change my use Function:

def use[F[_]](implicit U: Users[F], G: Groups[F], M: Mails[F], T: Transactor[F]) = for {
  _ <- T.trans(U.doSomething *> G.doSomething)
  _ <- M.doSomething
} yield ()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment