BEAR.Sunday

アプリケーションコンテキスト

More than 3 years have passed since last update.

アプリケーションコンテキスト

アプリケーションがどのように振る舞うかを決定するのがアプリケーションコンテキストです。

例えば、環境(prod / stage / develop)によって、DBの接続先が変わったり、キャッシュの使用の有無があったりします。これはどのフレームワークにも用意されている機能だと思います。

1つのサイトなのにHTMLサイトとAPIサイトを別に開発することがあります。そうではなくて1つのHTTPアプリケーションをつくってHTMLとAPIという風に振舞いを変える...というプレゼンテーションをREST API界の大御所中の大御所、Mike Amundsenさんの前で以前行ったことがあります。(緊張しました!)
http://koriym.github.io/http-is-one-thing/#/

言語もアプリケーションコンテキストの1つです。HTTPアプリケーションなのか、コンソールアプリケーションなのか、他にも通常バージョン、特別プロモーションバージョンなど1つのアプリケーションの振舞いを変えたい場合があるかもしれません。

0.xでは

それを現在のBEAR.Package 0.xではアプリケーションコンテキスト変数$contextとアプリケーションスクリプトの実行シークエンスを変えることで実現していました。

$contextは依存解決を決定します。たとえばdevelopならキャッシュは効きません。それにprod.phpというアプリケーションの実行を決めるアプリケーションスクリプトで実行することができました。構成はdevelopだけど実行はprodということはできました。

アプリケーションオブジェクトはDIで決定、実行はスクリプトです。

new ProdModule();
new ApiModule();  // これらのモジュールがアプリケーションオブジェクトを決定

アプリケーションコンテキストは単純な文字列として表されるので、キャッシュキーにも使えます。prod, develop, apiという3つのコンテキストキーを決めましたがアプリケーション要求で追加できます。

問題点

複数のコンテキストにどう対応するかという問題がでてきました。@mackstarさんからはAPIのdevelopはどうしたらいいのか?という質問をうけ、配列にして対応することで一時的な対処もしました。複数の実行スクリプトがメリットでもあるのですがさらに簡潔するため1つにならないかということも検討しました。

コンフィギュレーションとコンテキストの境界もあいまいでした。モジュールの中で他のモジュールをinstallしたり書き換えたりすることもできますが、一方でdbの接続設定などは変数を合成してdev, prodという環境値の優先順位を決定していました。他のFWと比べても特別複雑なものではなかったのですが、(設計上の)十分な集約が行われていないという認識をもつようになりました。

またデフォルトのアプリケーションの振舞いはdevelop ? prod ? どちらがいいのでしょうか?どうやって決めたらいいのでしょうか?

デコレーション可能なアプリケーション

アプリケーションをどのように振る舞うか選択するのではなく、素のアプリケーションを必要なコンテキストで装飾(デコレーション)していくことで解決できないか考えてみました。

図でかくと、...いやPHPで書いてみましょう!
こんな感じです。

0.xの単一コンテキストのイメージはswitch文で表せます。モードによって振舞いがswtichします。

switch ($context) {
    case 'api':
        // API出力
        break;
    case 'develop':
        // developとしての構成;
        break;
    case 'prod':
        // prodとしての構成;
        break;
}

そうではなく、GoFのデコレーションのイメージです。

$app = new Prod(new Hal(new Api(new App))));

素のアプリケーション(App)をApiアプリとして扱い、フォーマットは素JSONではなくHal、それをプロダクションProdとアプリケーションを装飾しています。

素のアプリケ−ションは開発用(develop)でもなくプロダクション用(prod)でもなく(bare)です。開発用の出力もないし、Prod用のキャッシュもありません。ただ最も単純に動作するだけのプログラムです。

DIとAOPで構成

これのアプリケーションオブジェクトをDIで構成するとこうです。

$injector = new Injector(new ProdModule(new HalModule(new ApiModule(new YourApp)))));
$app = $injector->getInstance(AppInterface::class);

パフォーマンスを考えるとこのnewの集合もキャッシュしたいのでBootstrap::newAppというメソッドを用意してこのようになりました。

$appName = 'Koriym\MyFirstApp';
$context = 'prod-hal-api-app';
$app = (new Bootstrap)->newApp(new AppMeta($appName), $context, new ApcCache);

アプリケーションの名前(vendor/app)とハイフン区切りでコンテキストに応じたアプリケーションオブジェクトがつくられます。

アプリケーション内ではオブジェクトが構成されているので振舞いを状態に応じて変える必要はありません。

if ($context === 'api') {
 $this->showAsJson($page);
} // などではなく

echo $page; // APIのときはjson
echo $page; // HTMLのときは

まとめ

素のアプリケーションは値だけを持ち、なんとかJSONに変換できるだけのアプリです。これは値だけなのでテストが簡単です。つぎに(HTMLなどの)表現が与えられるのですが値のテストが出来ていれば、表現だけのテストを行えばOKです。ソフトウエアをブラックボックスにして一番外側のIFから振舞いだけをみるようなテストを最小限にすることができます。

一度つくったものに外側からデコレーション可能であれば、リソースを情報コンポーネントとして再利用することができます。レガシーを本当の意味でのレガシー(資産)として扱うことが容易になるかもしれません。もしそれが可能になるとしたら、素晴らしいと思いませんか。