tldr; Use enter
to inject database information, monads and monad transformers rear their ugly heads but not for long.
This is a multi-part blog-post!
From what I can tell, there are 3 main approaches to getting the database management object (whatever that is) into the app:
Try WAI middleware, Drop the DB in the Vault (need to pass just the generated Vault key around, or store it as a global)
Create a custom combinator that modifies the request/context (see Part 1)
Modify the monad stack in which your app in servant runs, and use enter
, with a transform function to make it fit the Handler
mold
In Part 1, I let slip that the combinator creation approach was actually the wrong way to do it. Since I’m not a huge fan of the first option, all that I was left with was figuring out the third option. Luckily, some help from the developers on the github issue I opened made things much easier.
Here are some great resources
The basic jist is to actually use a different monad than the default monad that servant uses, for ALL my handlers. This way, I can store some information in the new monad, and it will be accessible from every handler. Of course, since I want to maintain all the functionality that comes with the servant monad like throwError
, I need to wrap the default monad with my own custom one. Servant supports this, by providingr a function called enter
that will take a monad transformer and allow you to wrap the default one to extend functionality.
At this point, I’m starting to read the code, github issue comments, and anything else that will help me understand how to use enter
. Since I know that all I need to do is store a little bit of state to be accessed from each handler, State
seems like the right monad to use for this problem. I spent some time figuring out where the monad transformer should go. Application
(from WAI
) is the wrong spot, but Server
(from Servant
) is the right spot – it’s a type synonym for ServerT api Handler
. Ideally, I should be able to replace the Handler
monad (the default) with my own BackendMonad
, with enter
.
I guessed that the best place for me to put this monad transformation code was in startApp
, where I call stuff like serve
and attach application middleware.I also connect to the database in startApp
so it’s a perfect place to put all this stuff together. “Putting the stuff together” generally entailed:
Referring to the servant documentation on using another monad for handlers.
Writing a function (based on examples) that makes the natural translation between the monads, using evalState
since there’s probably no need for the state to hang around after the handler does it’s thing
This required importing Control.Natural, and while I definitely can’t say I understand the type machinery there, it is straightforward at 10,000 feet: defining a transformation (that servant will later use automatically) from State SqliteBackend
(my custom monad) to Handler
(the default monad).
Run into some problems doing the natural transformation fn myself, particularly with types not matching up State SqliteBackend :~> Handler
!= StateT SqliteBackend Data.Functor.Identity....
. Unfortunately, I couldn’t quite figure out how to write the transformation function myself, thought the concept was so simple at a high level :(
Take a step back, look at the documentation around enter (https://hackage.haskell.org/package/servant-server-0.10/docs/Servant-Server.html#g:5)
Use the utilities provided, discover evalStateTLNat
(“L” for lazy) and evalStateTSNat
(“S” for strict), not sure of the difference, but they should automatically generate a natural transformation function for me
After getting all this stuff added in, handlers were finally equipped (once I changed their type signatures), the backend was finally being given to my handlers, the “right” way. Here’s what the type signature of my login endpoint looks like:
login :: UserEmailAndPassword -> WithSqliteBackend Handler (EnvelopedResponse SessionInfo)
Reading this from left to right, it’s a function that takes in a UserEmailAndPassword
object, and produces the monad WithSqliteBackend
, which when executed, will return a Handler
, which when executed will return an EnvelopedResponse
which contains SessionInfo
. Reading type signatures like this is part of what makes Haskell intoxicating, the clarity is amazing.
So, if I had chose to do this project in Golang, Clojure, Python, Ruby, or any other language I’m more familiar with I might have been more productive, but I genuinely enjoyed using Servant and Haskell to create this API. I like to expand my programming mind, whenever I can, and while this was a lot of trouble to go through to just put a backend connection in an application that serves a simple HTTP API, the time I spent thinking in the abstract helps me become a better Haskell programmer, and a better programmer in general.