Servant と Relational Record でウェブアプリケーション開発
Servant とは
Servant は型レベルプログラミングによって、ウェブアプリとしてのインターフェースと実装との差異を防ぐことのできるウェブアプリフレームワークです。
haskell-servant.readthedocs.io
日本語記事としては lotz さんのこちらが分かりやすいので、参考にしてください。
Haskell Relational Record とは
Haskell Relational Record は言語内 DSL によって SQL を生成するもので、正しくない SQL に相当するものは型エラーとなります。
この2つを組み合わせることで、ユーザーからのリクエストから DB 操作を経てレスポンスの返答まで型に守られて開発ができるようになります。
この記事で解説するソースコードはこのリポジトリーで公開しています。
Stack の resolver は 12.26 を使用しています。
作るもの
下記のようなインタフェースを持ったアプリを作ります。
/books で返る JSON は次のような形式です。
{ "ABC of Read": { "name": "ABC of Read", "auther": "Mary" }, "The Good Text": { "name": "The Good Text", "auther": "John" } }
/book/1 で返る JSON は次のような形式です。
{ "name": "The Good Text", "auther": "John" }
ハンドラーの型を拡張する
DB 接続を扱うということは、ハンドラー内で接続にアクセスできないといけません。ここでは Reader モナドトランスフォーマーを Servant のハンドラーに積みます。ロガーも使うのでその上に Logging モナドトランスフォーマーも積みました。
type Handler = LoggingT (ReaderT (Pool Connection) Servant.Handler)
Api
型とそれぞれのハンドラーが定義されている場合、ハンドラー拡張前では Application
型の値は次のようになっているでしょう。この部分は積んだモナドトランスフォーマーをはがすように書きかえなければいけません。
app :: Application app = serve api server api :: Proxy Api api = Proxy server :: Pool Connection -> Server Api server pool = Index.handler :<|> Books.handler :<|> Book.handler
次のように書きかえます。
makeApp :: IO Application makeApp = do pool <- createPool connect disconnect 1 -- stripes 1 -- time for keeping open 5 -- resource per stripe pure $ serve api $ server pool api :: Proxy Api api = Proxy server :: Pool Connection -> Server Api server pool = hoistServer api (flip runReaderT pool . runStdoutLoggingT) $ Index.handler :<|> Books.handler :<|> Book.handler
接続は再利用したいので resource-pool を用います。server
関数は hoistServer
関数を使ってモナドトランスフォーマーをはがす関数を埋め込みます。hoistServer
などについてはこちらを参考にしてください。リンク先の記事は最新版の Servant 0.12 に対応させました。
クエリーの発行
通常は runQuery'
関数に接続を渡して使用しますが、今回作ったハンドラーは Reader モナドで接続を持っていますので接続を渡す部分を隠すことができるはずです。次のようなラッパー関数を作りました。
runQuery' :: (ToSql SqlValue p, FromSql SqlValue a) => Query p a -> p -> Handler [a] runQuery' q p = do pool <- lift ask withResource pool $ \conn -> liftIO $ R.runQuery' conn q p
モジュール構成
今のところ次のようなモジュール構成にして開発しています。
- app
- Main.hs
- メイン関数を持つ
- Main.hs
- src
- ServantHrr.hs
この記事は IIJ の執務時間を使って書かれました。