(-> % read write unlearn)

All opinions expressed are solely my own and do not express the views or opinions of my employer.

enkan の env

Javaのモダンなウェブ・フレームワーク enkan の env 管理についてです。 env 周りのAPIや動作は直感的で使用例を見ればすぐ分かる感じです。 ただ、公式のドキュメントを見る限りだと、そのあたりの説明がまだあまり載っていないようだったので備忘のためまとめました。

enkan については、JJUG CCC 2016 Springのセッションで発表されていたのを聞いて知りました。 enkan 自体の概要は作者のkawasimaさんの http://qiita.com/kawasima/items/723646eb30a91d724352 の記事を参照してください。

実行環境による設定の切り替え

enkanには、実行環境ごとに異なる値(例えばデータベースのURLやメールの送信先など)を外部化して管理するための仕組みが普通にサポートされています。

enkan-coreのEnv.javaがまさにそれを実現しているクラスです。

kotowari-architypeで生成されるソースでも使われています。 例えばこんな感じです。

Env.getInt("PORT", 3000)

上記は実行環境ごとのportの値を取得しています。 第2引数の3000は、該当するキーが存在しなかった場合のデフォルト値です。

値を設定できる箇所

Env.javaは環境ごとの値を3つの場所から取得し、順にマージします。 Env.javaのスタティック・イニシャライザで次のように処理されており、

    static {
        readEnvFile();      // 1. <classpath>/env.properties ファイル
        readSystemEnv();    // 2. システム環境変数
        readSystemProps();  // 3. システム・プロパティ(例: java -Dmy.foo.bar=hoge)
    }

3つのメソッドはそれぞれ取得した値をスタティックなフィールドのマップにputしています。 なので、複数箇所で同じ値が設定されている場合は後勝ちになり(後に設定している値で上書きされ)ます。

middleware predicate

enkanでは、リクエストとレスポンスの処理がミドルウェア・パターンによって、モジュラーに着脱可能になっています。
この各ミドルウェアの有効/無効は、使用するミドルウェアを宣言する際にpredicate(述語)*1で条件付けをすることによって制御します。*2

enkan-coreでは、基本的な述語のクラスが一通り用意されており、 それらはenkan.util.Predicatesから簡単に利用可能です。

import static enkan.util.Predicates.NONE;
import static enkan.util.Predicates.envin;

// ・・・(中略)・・・

        WebApplication app = new WebApplication();
        Routes routes = Routes.define( r -> {
            r.get("/").to(IndexController.class, "index");
            // ・・・(その他ルーティングは省略)・・・
        }).compile();
        app.use(new DefaultCharsetMiddleware());
        app.use(NONE, new ServiceUnavailableMiddleware(new ResourceEndpoint("/public/html/503.html")));
        app.use(envIn("development", "test"), new StacktraceMiddleware());  // enkan.env が development かまたは test の場合のみ有効
        // ・・・(その他ミドルウェアは省略)・・・
        return app;
    }

各述語の実装はenkan.predicateパッケージ配下のクラスです。

この中のEnvPredicate.javaが、envIn()メソッド内で呼び出されている実装です。

その他の注意点や細かいこと

env.propertiesファイルの置き場所

クラスパス直下に置きます(プロジェクトのルートとかではないよ)。

キー文字列の正規化
  • キー文字列の大文字/小文字は無視されます。
  • また、キー文字列内の_.に変換されて解釈されます。

内部で小文字の.区切りに正規化(変換)しているためです。つまり、以下は全て同じ結果になります(極端な例ですが)。

  • Env.get("CLIENT.MAIL.SENDTO")
  • Env.get("client.mail.SendTo")
  • Env.get("client.mail.sendto")
  • Env.get("client_mail_sendto")
  • Env.get("client.mail_sendto")

propertiesファイルでは.区切り小文字で、環境変数では_区切りの大文字で書くのがよく見るスタイルなので、その差を吸収するような挙動なのかなと思います。

実行環境ごとの middleware の切り替え

enkan.util.Predicates#envIn()は、実行環境によってmiddlewareを切り替えるためのpredicateです。
具体的には、enkan.envというキーの値が用いられ、その値がenkan.util.Predicates#envIn()の引数(複数のうちどれか1つ)と一致しているかどうかで判定します。*3
env.propertiesにも環境変数にもシステム・プロパティにも該当のキーがない場合は、デフォルト値としてdevelopmentが使用されます。

まとめ

  • Env.javaクラスで実行環境ごとの値の管理が可能。
  • (1)<classpath>/env.properties → (2)環境変数 → (3)システム・プロパティ の順に読み込まれ、同じキーに紐づく値は上書きされる。
  • enkan.util.Predicates#envIn()メソッドでenkan.envの値による Middleware の制御が可能。

感想

REPLドリブンで開発できるのはやっぱり楽しい。

参考

*1:「述語」という語自体は、一般的にはtrue/falseの真偽値を返す関数のことを指すようです。enkanでは java.util.function.Predicate を一部拡張したクラスとして表現していました。

*2:厳密には、一度宣言した後も述語を切り替えることが可能であり、それがenkanの特長でもあるようです。

*3:つまり、 env.properties ファイル内の enkan.env の値や ENKAN_ENV 環境変数の値などが使用されます。