Getting started with servant, part 2
enter to inject database information, monads and monad transformers rear their ugly heads but not for long.
Multi-part blog post alert
This is a multi-part blog-post!
What are the ways to pass database information into an application using servant?
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
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.
Refresher on monads & monad transformers
Here are some great resources
Monad transforming the default monad servant
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.
WAI) is the wrong spot, but
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
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
evalStatesince 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.
Why do all this?
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.