Passing a DB connection to handlers in Servant

Posted on 2017-03-03 by Oleg Grenrus servant

This write-up is motivated by discussion in servant/#704 issue. I try to summarize the main points.

As this is a literate haskell file, we'll need to do a small prelude dance:

#The problem

The issue started as instance XY-problem:

  • Y: Docs explaining how to actually create a full combinator (ex. one to create/store a DB connection)
  • X: How to pass a db connection to the handlers.

I won't answer to the Y, how to write combinators is different topic (have to write about that later). Let's see how to deal with X, by implementing a small Cat CR(UD) API:

Now we'll need to implement the api, we'll write a basic Haskell functions, which we would write anyway, we could reuse them in a console application, for example.

And the problem is that if we try to do

-- THIS DOESN'T WORK
app :: Application
app = serve api $ createCat :<|> readCat

it will fail with a type-error message from GHC. Obviously, GHC cannot conjure Connection for us. We need to pass it in somehow.

#Partial application

Partial application is a simple tool. We can partially apply the implementation to fit into type required by serve. We'll make a situation a bit more interesting by using a connection pool:

As you can see we'd need to wrap every handler in withResource1. It's not very elegant, but it works. And is very simple to understand.

#Enter

servant offers the enter utility, which let's you to remove this kind of boilerplate. We'll rewrite our handlers in MTL-style, with a MonadDB type class. For the sake of example let's also add a MonadLog from log-base to the first endpoint.

Looks good, but how we'll pass a connection (and a logger)? The answer is obvious, when you know it: we'll need to use a concrete monad implementation, for example:

And now enter will to magic:

The nt (for natural transformation) tells how to transform the concrete monad H into servant's Handler. The enter machinery walks thru Server-like type and applies that transformation, our endpoints are instantiated to H in the process, so everything works out nicely.

If we used ReaderT (Connection Pool) Handler as our concrete monad, then we could use runReaderTNat to construct nt.

The enter is most useful when you have polymorphic handlers defined with mtl-like monad type-classes, so you can instantiate them all with the same concrete monad at then end. Note: that if we had concrete LogT Handler in some handler, and ReaderT (Pool Connection) Handler in some other one, enter won't help!

So to conclude:

  • start with partial application to pass arguments into handlers
  • later you may transfer to use fancier enter.

Alp Mestanogullari summarised it well: gradually reach for fancier things as your needs grow, never when it's not required.


You can run this file with

fetch the source from https://gist.github.com/phadej/4757105da52c1a1c946abe3b204ec500

Site proudly generated by Hakyll