Background
I've written a simple Servant application that stores some information in an SQLite db file. Also, I created a generic function that runs a DB query:
{-# LANGUAGE OverloadedStrings #-}
type Db m a = ReaderT SqlBackend (NoLoggingT (ResourceT m)) a
dbFileName :: Text
dbFileName = "./myDb.db"
runAction :: MoinadUnliftIO m => Db m a -> m a
runAction = runSqlite dbFileName
There's also a helper function that converts the DB action into a Servant Handler:
convert :: IO a -> Handler a
convert = Handler . ExceptT . try
withDb :: Db IO a -> Handler a
withDb = convert . runAction
This works and my Handlers respond as expected to HTTP calls, but sometimes they collide with one another and one of the calls attempts to access the SQLite file while it's busy by another call, which results in an exception.
If my understanding is correct, adding a Pool
to my runAction
function should solve this problem. However, it seems that I'm not experienced enough with Haskell, as I can't get all the types to match nicely.
Question: how to add Pool to my app?
I found a few snippets online (e.g. https://github.com/haskell-servant/example-servant-persistent/blob/master/src/App.hs). After several attempts and few changes that I don't necessarily understand I came up with the following piece of code:
-- slightly different now
type Db m a = ReaderT SqlBackend m a
dbPool :: Pool SqlBackend
dbPool = do
pool <- createSqlitePool dbFileName 5
runSqlPool (runMigration migrateAll) pool
pool
runAction :: MoinadUnliftIO m => Db m a -> m a
runAction action = runSqlPool action dbPool
This one doesn't compile and I get the following error message:
No instance for (Monad Pool) arising from a do statement
In a stmt of a 'do' block: pool <- createSqlitPool dbFileName 5
Can someone please help me with this and - if possible - recommend valuable readings to get a better understanding of Haskell? I really like the language, got the basics, but I more real-life aspects of it quite overwhelming.
EDIT
Following the hints presented in the answer I found a solution. It wasn't straightforward at all, so I can't put the entire code here, but the key ideas are here:
1.) It turned out that I can't just "create a Pool
and then use it here and there. A Pool
is contained inside a monad, so I need to use it in a monadic way.
2.) The final solution looks like the snippet presented here:
- Use a type alias
type DbPool = Pool SqlBacked
instead ofConnectionPool
used in the snippet. - Declare all the
Servant
handlers as functionsDbPool -> Handler a
. - Create a
DbPool
in Main. - Pass the created
pool
to every function that needs it.
It works, but I've got a feeling it's massively overcomplicated. As time progresses I'll take a look if there's a way to simplify it.