Servant とは

Servant は型レベルプログラミングによって、ウェブアプリとしてのインターフェースと実装との差異を防ぐことのできるウェブアプリフレームワークです。

haskell-servant.readthedocs.io

日本語記事としては lotz さんのこちらが分かりやすいので、参考にしてください。

qiita.com

Haskell Relational Record とは

Haskell Relational Record は言語内 DSL によって SQL を生成するもので、正しくない SQL に相当するものは型エラーとなります。

khibino.github.io

この2つを組み合わせることで、ユーザーからのリクエストから DB 操作を経てレスポンスの返答まで型に守られて開発ができるようになります。

この記事で解説するソースコードはこのリポジトリーで公開しています。

github.com

Stack の resolver は 12.26 を使用しています。

作るもの

下記のようなインタフェースを持ったアプリを作ります。

/ HTML が返る

/books 書籍情報のマップが JSON で返る

/book/<id> id で指定された書籍情報が JSON で返る



/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 1 5 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 に対応させました。

qiita.com

クエリーの発行

通常は 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 メイン関数を持つ

src ServantHrr.hs app/Main.hs から使われるインターフェースを公開する Data Common.hs ハンドラーを横断して使用するデータ型を定義する Book.hs Book ハンドラーで使用するデータ型を定義する … Relation Book.hs データベースのテーブルと 1:1 対応し、HRR で生成される定義を持つ Handler Common.hs ハンドラーを横断して使用する関数を定義する Book.hs Book ハンドラーを定義する … Api Api 型を定義する DataSource.hs HRR 用 Secret.hs DB 接続用のパスワードなど VCS 管理下におかない



この記事は IIJ の執務時間を使って書かれました。