アプリケーションコンテキスト
アプリケーションがどのように振る舞うかを決定するのがアプリケーションコンテキストです。
例えば、環境(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で決定、実行はスクリプトです。
- prod https://github.com/koriym/BEAR.DemoApps/blob/develop/Demo.Sandbox/bootstrap/contexts/prod.php
- dev https://github.com/koriym/BEAR.DemoApps/blob/develop/Demo.Sandbox/bootstrap/contexts/dev.php
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から振舞いだけをみるようなテストを最小限にすることができます。
一度つくったものに外側からデコレーション可能であれば、リソースを情報コンポーネントとして再利用することができます。レガシーを本当の意味でのレガシー(資産)として扱うことが容易になるかもしれません。もしそれが可能になるとしたら、素晴らしいと思いませんか。