Each request not only spins up a new HTTP manager but also performs a "withSocketsDo" which may cause unnecessary additional latency on Windows.
The API should allow a connection pool via inputting a manager.
Thoughts though - this makes the signature on many of the API functions rather longer than I would like. The signature being something like:
Manager -> Account -> String -> ...
Would it be worth making this (Manager,Account,String) -> ... and use a Reader Monad to allow:
(Manager,Account,String) -> ...
let key = ...
columns = ...
runTableStorage mgr acct tbl $ do
case queryEntity key of
Left _ <- insertEntity key
Right _ <- mergeEntity key columns
Problem: there's one oddball API function, queryTables. It would take only an account and a manager. Given that it's the only odd one out, there are two options: make it use the monad so you can do tables <- queryTables within a runTableStorage, or let its API require reiterating the manager and account.
tables <- queryTables
I like the idea of moving things into an appropriate monad as the API becomes more complicated, but I'm not sure the table name belongs in the context of the reader monad. I'd rather read the manager and the account and provide the table name as an argument personally.
A third option, since we also have errors to deal with as a side effect, is to turn the signatures into something like
createTable :: (MonadReader Manager m, MonadReader Account m, MonadError m) => String -> m ()
Then people can use their own monad stack with the API.
We could roll up the three monads into a new class if that tidies things up.
class (MonadReader Manager m, MonadReader Account m, MonadError m) => MonadTableStorage m
createTable :: (MonadTableStorage m) => String -> m ()
What do you think?
I'll agree re: table, if only because it makes it easier to interact with multiple tables simultaneously.
Well I don't think a function can be simultaneously MonadReader t m and MonadReader t' m unless t ~ t'?
MonadReader t m
MonadReader t' m
t ~ t'
Either way, I'm fine with an API like that although I would probably not use the typeclass.
Regarding MonadReader, I think it can work but you'd need to disambiguate the call to ask with a type signature.
My vote would be for either that or a custom monad (transformer) but the downside there would be the need to write instances for all the other mtl classes if we wanted to interop.
What do you think about this?
API looks like:
let acct = ...
let conf = TableConf Nothing acct
withTableStorage conf $ do
tables <- queryTables
if not $ "test" `elem` tables
then createTable "test"
else return ()
insertEntity "test" ...
I intend to add additional properties to TableConf such as an Maybe Proxy, a retry count, and clean up the error handling on the error type - right now, obviously, it's just returning Unknown or Other error, I'd like to be more specific.
Looks good to me.