-
Notifications
You must be signed in to change notification settings - Fork 73
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
Polysemy and Persistent: transactions at business level #361
Comments
as far as I can tell from the docs, it should be possible with something like bracket (embed (runReaderT acquireSqlConnFromPool pool)) (release ???) (updateFoos =<< findFoos) which you could build into the As for the interpreter, it would look something like this: interpretTransaction ::
Members [Resource, Embed IO] r =>
InterpreterFor Transaction r
interpretTransaction =
interpretH \case
InTransaction ma -> bracket (embed ...) (release ...) (\ _ -> raise . interpretTransaction =<< runT ma) another approach would be using the forklift: withLowerToIO \ unlift _ -> runReaderT acquireSqlConnFromPool pool >>= \ acq -> with acq (unlift (raise ... ma)) |
@tek Thank you for your answer!
interpretTransaction :: Members [Resource, Embed IO] r => SqlPool -> InterpreterFor Transaction r
interpretTransaction pool = interpretH \case
InTransaction ma ->
bracket
(embed . runResourceT $ runReaderT acquireSqlConnFromPool pool >>= allocateAcquire)
(\(releaseKey, _) -> embed $ release releaseKey)
( \(_, sqlBackend) -> do
polysemyAction <- runT ma
raise . interpretSqlPersistM sqlBackend . databaseToSqlPersistM . interpretTransaction pool . raiseUnder2 $ polysemyAction
)
interpretSqlPersistM :: Members '[Embed IO] r => SqlBackend -> InterpreterFor (Embed SqlPersistM) r
interpretSqlPersistM sqlBackend = interpret \case
Embed sqlAction -> embed . runResourceT . runNoLoggingT $ runReaderT sqlAction sqlBackend
databaseToSqlPersistM :: Members '[Embed SqlPersistM] r => Sem (Database ': r) a -> Sem r a
databaseToSqlPersistM =
interpret \case
FindFoos -> selectList [] []
SaveFoos foo -> repsert (makeKey foo) foo
Thanks again 🙇 EDIT: This doesn't work, each database query is executed in its own transaction nonetheless 😞 |
oh wow, didn't expect |
@sir4ur0n |
it definitely looks like it's using a separate connection for each transaction |
Yup, I forgot to come back to this issue, but overall this doesn't work as I expected. I never managed to make it work, and because error messages are just too confusing/frustrating for higher order effects in Polysemy, I gave up (and so did my team) 😞 Feel free to reopen if others are interested in this kind of issues. I feel like to be able to express this, one would need to be able to express additional information about the If somebody finds a way to make this work, I would be more than happy to switch my application code, but I spent already too much time trying to make this work in vain. Good luck! |
I think something like this should work: data Transaction conn m a where
InTransaction :: (conn -> m a) -> Transaction conn m a
makeSem ''Transaction
data Foo
data Database conn m a where
FindFoos :: conn -> Database conn m [Foo]
SaveFoo :: conn -> Foo -> Database conn m ()
makeSem ''Database
runTransaction ::
(Sem r conn) ->
(conn -> Sem r ()) ->
Sem (Transaction conn ': r) a ->
Sem r a
runTransaction = <some-impl>
runDatabase :: <some-signature>
runDatabase ... = interpret \case
FindFoos conn -> do
...
runSqlConn <action> conn
...
...
businessLogic =
...
inTransaction \conn -> findFoos conn
... it may not be as "clean" as what you originally wanted, as you are essentially passing the connection around, but I don't see a reason why it wouldn't work. I'll try to do a concrete implementation later these days. |
Here's a proper solution, by @KingoftheHomeless, in which you don't have to link your |
This should now be easier, by combining @KingoftheHomeless' solution, packaged here. I also wrote a small adapter for my particular |
I reckon it's better to get the
|
I gave up on using that, because the solution proposed by Love is cleaner:
|
@googleson78 which solution are you referring to here? |
FYI I demonstrated a complete implementation here |
Hi,
I've been pulling my hair for the last couple of days on this issue, but I can't manage to find a solution, I would appreciate if anyone can help me!
Goal
Use together Polysemy and Persistent (actually there are many more layers involved like Servant, but I hope they are irrelevant to my problem), with granularity over Persistent (and thus SQL) transactions, that is, run several SQL calls in a single transaction.
What I currently have (simplified)
My problem is that
findFoos
andsaveFoo
don't run in the same transaction. AFAIU in Persistent, a transaction is a single execution ofliftSqlPersistMPool
, but my interpreter executes it for eachDatabase
action.I would like my business code to decide what belongs in a single transaction (atomicity), not the interpreters.
What I wish I could write (not all code is repeated)
But I can't manage to write a chain of interpreters that compiles/makes sense.
Note 1: I am aware of higher-order effects (we already have some in our codebase, e.g. to manage logging contexts) and I feel like this could be used, but again, I did not manage to wrap my head around it.
Note 2: I would be ok with being forced to run each
Database
action inside aTransaction
, i.e. having to write as such:Actually this might even be safer, to force the business code to specify what belongs in a transaction 🤔
Any help is appreciated 🙏
The text was updated successfully, but these errors were encountered: