Docker で Haskell アプリケーションを作成 - MongoDB 利用
MongoDB へ接続する Haskell アプリケーションを Docker で作成してみました。
以下の Docker イメージを使用します。
ビルドツールは stack を使って、MongoDB への接続には以下のライブラリを使います。
今回のソースは http://github.com/fits/try_samples/tree/master/blog/20170529/
(1) Docker コンテナの実行
Docker で MongoDB と Haskell のコンテナを実行します。
コンテナ間の連携には、deprecated となっている Link 機能(–link)は使わずにユーザー定義ネットワークを使う事にします。
(1.1) bridge ネットワークの作成
bridge ネットワークを新規作成します。
$ docker network create --driver bridge br1
(1.2) MongoDB コンテナの実行
作成したネットワークへ参加するように --net=<ネットワーク名>
を使って MongoDB を docker run します。
$ docker run -d --name mongo1 --net=br1 mongo
(1.3) Haskell コンテナの実行
Haskell も同様に docker run します。
ここでは Docker ホスト側の /vagrant/work をコンテナの /work へマウントしています。
$ docker run -it --name hs1 --net=br1 -v /vagrant/work:/work haskell /bin/bash root@・・・#
確認のため Haskell コンテナ内から MongoDB のコンテナへ ping してみます。
ping 例
root@・・・# ping mongo1 PING mongo1 (172.18.0.2): 56 data bytes 64 bytes from 172.18.0.2: icmp_seq=0 ttl=64 time=0.640 ms 64 bytes from 172.18.0.2: icmp_seq=1 ttl=64 time=0.257 ms ・・・
(2) Haskell アプリケーションの作成
Haskell のコンテナで MongoDB へ接続するアプリケーションを作成します。 (以降の処理は (1.3) で起動した Haskell コンテナ内で実施します)
(2.1) プロジェクトのひな型作成
stack new <プロジェクト名> [<テンプレート名>]
でプロジェクトのひな型を作ります。
プロジェクト作成
root@・・・# cd /work root@・・・# stack new sample1 Downloading template "new-template" to create project "sample1" in sample1/ ... ・・・
プロジェクト名のディレクトリが作られ、各種ファイルが生成されます。
(2.2) 依存ライブラリの取得
MongoDB driver for Haskell を使用するため、<プロジェクト名>.cabal
ファイルの library/build-depends へ mongoDB
を追記します。
今回は Control.Monad.Trans
も使うので mtl
も追記しています。
sample1/sample1.cabal
・・・ library ・・・ build-depends: base >= 4.7 && < 5 , mongoDB , mtl default-language: Haskell2010 ・・・
stack build
でビルドすると未取得の依存ライブラリをダウンロードします。
プロジェクトのビルド(依存ライブラリの取得)
root@・・・# cd sample1 root@・・・# stack build --allow-different-user ・・・ mtl-2.2.1: download mtl-2.2.1: configure mtl-2.2.1: build mtl-2.2.1: copy/register ・・・
ここで --allow-different-user
オプションを付けていますが、今回のケースではこのオプションを付ける必要がありました。(-v でマウントしたディレクトリを使用した事が原因だと思われる)
なお、–allow-different-user 無しで stack build
すると以下のようになりました。
root@・・・# stack build You are not the owner of '/work/sample1/'. Aborting to protect file permissions.Retry with '--allow-different-user' to disable this precaution.
(2.3) MongoDB 接続アプリケーションの実装
とりあえず someFunc
という関数名をそのまま使う事にします。(変更する場合は app/Main.hs も変更します)
OverloadedStrings
と ExtendedDefaultRules
の言語拡張が最低限必要となるようです。
MongoDB driver for Haskell では、MongoDB に対する処理を Action m a
(MonadIO m) で定義し access
関数で処理すればよさそうです。
ここで Action
は type Action = ReaderT MongoContext
と定義されています。
MongoDB のコレクションへ複数ドキュメントを追加するには insertMany
や insertMany_
関数が使えます。(ドキュメント追加の用途では insert
や insertAll
関数等もあります)
関数名の最後の _
の有無は、作成したドキュメントの _id
の値を返すかどうかの違いのようです。(_
の付いている方は返さない)
今回は _id
の値は不要なので insertMany_
の方を使いました。
MongoDB へ登録するドキュメントは [<項目名> =: <値>, ・・・]
という形式で定義できます。
find
関数の結果は Action m Cursor
なので、rest
関数(Cursor -> Action m [Document]
) をバインド(>>=
)して Action m [Document]
を取得しています。
接続先の MongoDB ホスト名は MONGO_HOST
環境変数から取得するようにしてみました。
sample1/src/Lib.hs
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ExtendedDefaultRules #-} module Lib ( someFunc ) where import System.Environment import Control.Monad.Trans (lift) import Database.MongoDB someFunc :: IO () someFunc = do mongo <- mongoHost -- MongoDB 接続 pipe <- connect (host mongo) access pipe master "sample" proc close pipe mongoHost :: IO String mongoHost = getEnv "MONGO_HOST" proc :: Action IO () proc = do -- items コレクションへドキュメント追加 insertMany_ "items" [ ["name" =: "item1", "value" =: 1], ["name" =: "item2", "value" =: 2] ] allItems >>= printDocs -- items コレクションの全ドキュメント取得 allItems :: Action IO [Document] allItems = rest =<< find ( select [] "items" ) -- ドキュメントの出力 printDocs :: [Document] -> Action IO () printDocs docs = lift $ mapM_ print docs
(2.4) ビルドと実行
それでは、ビルドして実行します。
ビルド
root@・・・# stack build --allow-different-user ・・・ /work/sample1/.stack-work/install/x86_64-linux/lts-8.15/8.0.2/bin Registering sample1-0.1.0.0...
.stack-work ディレクトリへ(ビルドの)成果物が生成されます。
実行するには stack exec <実行ファイル名>
で実行するか、実行ファイル(例 .stack-work/install/x86_64-linux/lts-8.15/8.0.2/bin/sample1-exe
)を直接実行します。
実行ファイル名はデフォルトで <プロジェクト名>-exe
となるようです。
今回は環境変数から MongoDB のホスト名を取得するようにしたので、MONGO_HOST
環境変数へ MongoDB のコンテナ名を設定してから実行します。
実行
root@・・・# export MONGO_HOST=mongo1 root@・・・# stack exec sample1-exe --allow-different-user [ _id: 592a7ffb2139612699000000, name: "item1", value: 1] [ _id: 592a7ffb2139612699000001, name: "item2", value: 2]