CLOVER🍀

That was when it all began.

Flapdoodle OSS/Embedded MongoDBを使う

タイトルでいかにも組み込みMongoDBみたいなものを期待しそうですが…まあ、プロダクト名としてはウソはついていません。

以下のライブラリを使って、Javaで組み込みMongoDBっぽいことができるようです。っていうか、MongoDBってJavaじゃないですけど…。

Embedded MongoDB

自分自身、ちょっと組み込みMongoDBっぽいものが欲しいと思って試してみました。

で、結果ですが上記ライブラリは以下のような動作をします。

  • プラットフォーム(OS、32bit/64bit)に応じて、適切なMongoDBバイナリをダウンロード
  • バイナリを展開してmongodコマンドなどを抽出し、内部的にmongodなどを起動

ということをラップするようなライブラリです。

内部では、同じくFlapdoodle OSSのEmbedded Process Utilというライブラリに依存しており、

Embedded Process Util

類似としてRedis、Memcached、Node.js、PostgreSQLのラッパーもあるようです。

このライブラリの目的ですが、基本的にはUnitTestで使うことを想定しているみたいです。インストールするバージョンや、バージョンごとのテストなどをするのに使いたい感じの模様。

ちなみに、これの存在を知ったきっかけはSpring Bootです。

Embedded Mongo

mongod以外にも、mongosやmongo(シェル)も使用できそうな感じですが、今回はmonogodのみとします。

では、試してみます。

準備

Maven依存関係としてh、以下の定義を行います。

        <dependency>
            <groupId>de.flapdoodle.embed</groupId>
            <artifactId>de.flapdoodle.embed.mongo</artifactId>
            <version>1.50.1</version>
        </dependency>

が、これだけだと実はMongoDBのドライバは含まれていないので、プログラムからMongoDBにアクセスする場合は別途MongoDBのドライバを足しておきます。

        <dependency>
            <groupId>org.mongodb</groupId>
            <artifactId>mongodb-driver</artifactId>
            <version>3.2.0</version>
        </dependency>

単純な使い方

以下のUsageを見ながら使うと(MongoDBのドライバのバージョン古そうですが…)、

Usage

こんな感じのプログラムになります。このサンプルでは、mongodを使ってMongoDBを起動して、そのあとクライアントからアクセスしてシャットダウンという流れになっています。
src/main/java/org/littlewings/embed/mongodb/App.java

package org.littlewings.embed.mongodb;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import com.mongodb.Block;
import com.mongodb.MongoClient;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import de.flapdoodle.embed.mongo.MongodExecutable;
import de.flapdoodle.embed.mongo.MongodProcess;
import de.flapdoodle.embed.mongo.MongodStarter;
import de.flapdoodle.embed.mongo.config.IMongodConfig;
import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;
import de.flapdoodle.embed.mongo.config.Net;
import de.flapdoodle.embed.mongo.distribution.Version;
import de.flapdoodle.embed.process.runtime.Network;
import org.bson.Document;

public class App {
    public static void main(String... args) throws IOException {
        MongodStarter starter = MongodStarter.getDefaultInstance();

        int port = 12345;
        IMongodConfig mongodConfig =
                new MongodConfigBuilder()
                        .version(Version.Main.PRODUCTION)
                        .net(new Net(port, Network.localhostIsIPv6()))
                        .build();

        MongodExecutable mongodExecutable = null;

        try {
            mongodExecutable = starter.prepare(mongodConfig);
            MongodProcess mongod = mongodExecutable.start();

            // MongoDBのドライバを使ったコード
            try (MongoClient client = new MongoClient("localhost", port)) {
                MongoDatabase database = client.getDatabase("testDatabase");
                MongoCollection<Document> collection = database.getCollection("testCollection");

                Map<String, Object> docSeed1 = new HashMap<>();
                docSeed1.put("name", "磯野カツオ");
                docSeed1.put("age", 11);

                Map<String, Object> docSeed2 = new HashMap<>();
                docSeed2.put("name", "磯野ワカメ");
                docSeed2.put("age", 9);

                Arrays.asList(docSeed1, docSeed2).forEach(d -> collection.insertOne(new Document(d)));

                FindIterable<Document> iterable = collection.find(new Document("name", "磯野カツオ"));
                iterable.forEach((Block<Document>) (d -> System.out.println("doc = " + d)));
            }

            mongod.stop();
        } finally {
            if (mongodExecutable != null) {
                mongodExecutable.stop();
            }
        }
    }
}

try-with-resourcesの中身は、MongoDBのドライバを使ったプログラムそのものなので、端折ります。

            // MongoDBのドライバを使ったコード
            try (MongoClient client = new MongoClient("localhost", port)) {
                MongoDatabase database = client.getDatabase("testDatabase");

MongoDBが使うポートなどの指定をして、MongodProcessを開始します。

        MongodStarter starter = MongodStarter.getDefaultInstance();

        int port = 12345;
        IMongodConfig mongodConfig =
                new MongodConfigBuilder()
                        .version(Version.Main.PRODUCTION)
                        .net(new Net(port, Network.localhostIsIPv6()))
                        .build();

        MongodExecutable mongodExecutable = null;

        try {
            mongodExecutable = starter.prepare(mongodConfig);
            MongodProcess mongod = mongodExecutable.start();

で、最後に停止、と。

            mongod.stop();
        } finally {
            if (mongodExecutable != null) {
                mongodExecutable.stop();
            }
        }

このプログラムを実行すると、初回はプラットフォームに合ったMongoDBをダウンロード、展開してきてmongodを起動します。取得するMongoDBは、「os.name」などで自動でプラットフォームを判定して、決めているようです。

Download PRODUCTION:Linux:B64 START
Download PRODUCTION:Linux:B64 DownloadSize: 40274671
Download PRODUCTION:Linux:B64 0% 1% 2% 3% 4% 5% 6% 7% 8% 9% 10% 11% 12% 13% 14% 15% 16% 17% 18% 19% 20% 21% 22% 23% 24% 25% 26% 27% 28% 29% 30% 31% 32% 33% 34% 35% 36% 37% 38% 39% 40% 41% 42% 43% 44% 45% 46% 47% 48% 49% 50% 51% 52% 53% 54% 55% 56% 57% 58% 59% 60% 61% 62% 63% 64% 65% 66% 67% 68% 69% 70% 71% 72% 73% 74% 75% 76% 77% 78% 79% 80% 81% 82% 83% 84% 85% 86% 87% 88% 89% 90% 91% 92% 93% 94% 95% 96% 97% 98% 99% 100% Download PRODUCTION:Linux:B64 downloaded with 9832kb/s
Download PRODUCTION:Linux:B64 DONE
Extract /home/your-user/.embedmongo/linux/mongodb-linux-x86_64-3.0.5.tgz START
Extract /home/your-user/.embedmongo/linux/mongodb-linux-x86_64-3.0.5.tgz extract mongodb-linux-x86_64-3.0.5/bin/mongod
Extract /home/your-user/.embedmongo/linux/mongodb-linux-x86_64-3.0.5.tgz noting left
Extract /home/your-user/.embedmongo/linux/mongodb-linux-x86_64-3.0.5.tgz DONE
[mongod output]note: noprealloc may hurt performance in many applications
[mongod output] 2016-01-02T20:07:40.725+0900 I STORAGE  [DataFileSync] warning: --syncdelay 0 is not recommended and can have strange performance
[mongod output] 2016-01-02T20:07:40.732+0900 I CONTROL  [initandlisten] MongoDB starting : pid=91397 port=12345 dbpath=/tmp/embedmongo-db-6337afb0-aa89-4b1d-8463-4506ed95d4a0 64-bit host=your-host

2回目以降は、MongoDBがダウンロード済みなのでこの部分はスキップされます。

なお、mongodの出力がずらずらと現れるのですが、その中にサンプルで書いたSystem.outの結果も混じってきます。

[mongod output] doc = Document{{_id=5687af7dfd5af964d0400240, name=磯野カツオ, age=11}}

mongodが出力したことになってますけど…System.outを奪ってるな…。

それでは、その他の動き、カスタマイズできる場所を見ていきます。

MongoDBのバージョンは?

先ほどの例では、以下の部分でMongoDBのバージョンを指定していました。

                new MongodConfigBuilder()
                        .version(Version.Main.PRODUCTION)

現時点では、これでバージョン3.0.5を指します。

バージョンを変える場合は、Version.Mainから指定するか

                new MongodConfigBuilder()
                        .version(Version.Main.V3_1)

もう少し細かいバージョンを指定する場合は、Versionに定義されているものを使用します。

                new MongodConfigBuilder()
                        .version(Version.V3_1_6)

現時点では、3.1.6が選択できる最も新しいバージョンです。

利用するポートは?

今回のプログラムではポートを固定で決めましたが、ライブラリの方に決めてもらうこともできるようです。

Use Free Server Port

保存したデータはどこに?

起動時にちょっとログに出ていましたが、デフォルトではシステムプロパティ「java.io.tmpdir」(要はJavaの一時ディレクトリ)にデータを置こうとします。

[mongod output] 2016-01-02T20:07:40.732+0900 I CONTROL  [initandlisten] MongoDB starting : pid=91397 port=12345 dbpath=/tmp/embedmongo-db-6337afb0-aa89-4b1d-8463-4506ed95d4a0 64-bit host=your-host

そして、終了時にファイルを削除してしまうため、デフォルトではデータは残りません。

この挙動を変更する場合は、データ用のディレクトリを明示的に指定する必要があります。

        int port = 12345;
        IMongodConfig mongodConfig =
                new MongodConfigBuilder()
                        .version(Version.V3_1_6)
                        .net(new Net(port, Network.localhostIsIPv6()))
                        .replication(new Storage("/path/to/data", null, 0))
                        .build();

これで、指定したパスにデータが保存され、なおかつ終了時も削除されることはなくなります。

この挙動が良いか悪いかは、用途次第ですね。

ダウンロードファイルや実行ファイルは、どこにあるの?

デフォルトでは、「$HOME/.embedmongo」ディレクトリに保存されます。

$ find $HOME/.embedmongo
$HOME/.embedmongo
$HOME/.embedmongo/linux
$HOME/.embedmongo/linux/mongodb-linux-x86_64-3.1.6.tgz
$HOME/.embedmongo/linux/mongodb-linux-x86_64-3.0.5.tgz
$HOME/.embedmongo/extracted
$HOME/.embedmongo/extracted/Linux-B64--3.1.6
$HOME/.embedmongo/extracted/Linux-B64--3.1.6/extractmongod
$HOME/.embedmongo/extracted/Linux-B64--3.0.5
$HOME/.embedmongo/extracted/Linux-B64--3.0.5/extractmongod

自分の環境では、「.embedmongo/linux」に入っているのがダウンロードしたMongoDBのアーカイブで、「.embedmongo/extracted」に入っているのが抽出されたmongodコマンドです。

ダウンロード元、保存先、ネットワークプロキシを設定したい場合は?

MongoDBのダウンロード元は、デフォルトでは「http://downloads.mongodb.org/(Windows)」、「http://fastdl.mongodb.org/(Windows以外)」になっています。このダウンロードURLや保存先、mongodコマンドの展開先や、取得時のプロキシ設定などをまとめて書くと、こんな感じになります。

        Command command = Command.MongoD;
        IRuntimeConfig runtimeConfig =
                new RuntimeConfigBuilder()
                        .defaults(command)
                        .artifactStore(
                                new ExtractedArtifactStoreBuilder()
                                        .defaults(command)
                                        .download(
                                                new DownloadConfigBuilder()
                                                        .defaultsForCommand(command)
                                                        // ダウンロード元
                                                        .downloadPath("http://yourhost/")
                                                        // プロキシ
                                                        .proxyFactory(new HttpProxyFactory("your-proxy", 8080))
                                                        // ダウンロードファイルの保存先
                                                        .artifactStorePath(new FixedPath("/path/to/store"))
                                                        .build()
                                        )
                                        // 展開したファイルの実行ファイル配置場所
                                        .extractDir(new FixedPath("/path/to/extracted"))
                        )
                        .build();

        MongodStarter starter = MongodStarter.getInstance(runtimeConfig);

最初の例とちょっとやり方が変わり、Command、IRuntimeConfigというものが登場し、

        Command command = Command.MongoD;
        IRuntimeConfig runtimeConfig =
                new RuntimeConfigBuilder()

これを元にしてMongodStarterを取得するような流れになります。

        MongodStarter starter = MongodStarter.getInstance(runtimeConfig);

各設定内容の意味は、コメントに書いてあるので見てもらえれば…。

ダウンロード先URLは、内部的にはjava.net.URLConnectionを使用しているので、「file:///〜」と指定すればあらかじめダウンロード済みのMongoDBのアーカイブを使用することも可能です。

注意事項としては、指定したURLの配下はOSなどのディレクトリが入った、ルールに沿ったファイル名、構成である必要があります。

例えば、指定するURLが

http://your-host/dist/

で、実行環境が64bit Linuxの場合、以下の場所にアーカイブが配置されている前提になっています。

http://your-host/dist/linux/mongodb-linux-x86_64-3.1.6.tgz

圧縮形式はWindowsの場合はzip、それ以外はtar.gzです。

カスタマイズの方法はREADMEに載っているのと、

https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo/blob/master/README.md

de.flapdoodle.embed.mongo.configパッケージを見るとよいでしょう。

https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo/tree/de.flapdoodle.embed.mongo-1.50.1/src/main/java/de/flapdoodle/embed/mongo/config