[FormalBears] Configurable BEAR.Sunday

## はじめに

2018年春に行われたBEAR.Sundayミートアップで、 [@iteman](https://twitter.com/iteman) さんよりBEARのメタフレームワークである `FormalBears` についてシェアされていました[^1]。FormalBears は限定公開のプロジェクトなのですが、私は作者のご承諾を得た上でそのフォーク版を仕事で使っています。

## モジュール設定言語機能

FormalBears には様々な機能がありますが、土台となる基本機能として位置づけられるのが「コンフィグレーション言語機能」です。下記のようなことができます。

1. 設定ファイルによる設定（YAML）とグラマー定義を通したコンパイル（[symfony/config](https://github.com/symfony/config) 統合）
2. 設定のマージ（デフォルトの設定、development / production といった環境毎のオーバーライド）
3. 環境変数統合（記述例： `url: '%env(DATABASE_URL)%'` ）

ひと言で言えば「Symfonyスタイル」と言えます。上記1. については既に紹介済みでした[^2]。この記事では上記2. について説明してごく簡単な実装デモを紹介します。

## 予備知識：アプリケーションコンテキストと環境

BEARではアプリケーションの構成が `アプリケーションコンテキスト` [^3][^4] で決まります（他のWAFでは環境変数の環境キー、たとえばSymfonyであれば `APP_ENV` の値によって環境モードが決定されますが、BEARでは枠組みが異なります）[^5] 。実務ではアプリケーションを実行している `環境` にもとづいて異なる設定に切り替える必要がありますから、ユーザは環境に対応するアプリケーションコンテキストを定義し（例： `prod` ,  `dev` ）、対応する `モジュール` （例： `ProdModule`, `DevModule`）を配置することになります。ここまではごく普通の、BEARユーザなら誰でもやっている作業です。

## デフォルト、環境毎のオーバーライド

ここからが本題です。アプリケーション全体として見ると設定はけっこう膨大になっていくものです。各モジュール毎にバラバラの規約で設定を配置していくとなると、アプリケーションのメンテナンス性に難が出てくるのではないでしょうか。

FormalBears は、

- １個のモジュールに対して１つの設定領域（名前付けした設定のグループ）」を定義して、
- モジュール単位で `デフォルト` を指定したり、
- 「１つの環境に対して１つの設定ファイル」で `環境毎のオーバーライド` を設定したりするフレームワークを提供します。

## ミニマムの実装例

簡単な例で見ていきます。お題として、仮に

- `Github のユーザー情報一覧を表示するAPIクライアント` を作っている、とします。
- 「APIのURL」、「表示したいユーザー名のリスト」の２つは設定項目だとします。
- 「APIのURL」は `共通設定` 、「表示対象ユーザー名のリスト」は、 `環境によって切り替えたい` とします。

FormalBears だと下図のような設定になります。
![スクリーンショット 2018-12-11 14.10.19.png](https://qiita-image-store.s3.amazonaws.com/0/15295/c310cefb-90d8-8dc8-719d-67907a7f9c63.png)



インジェクション方法等の詳細は省きますが、下記リポジトリに動作するコードを置きました。

[kumamidori/FormalBearsDemo](https://github.com/kumamidori/FormalBearsDemo)


FormalBearsにコンテキストを読ませるため、 `bootファイル` [^4]の実装をBEARインストールデフォルトから変えてあります。

## FormalBears の実装

FormalBears のサブセットフォーク版ソースコードを公開します。

- [FormalBears](https://github.com/kumamidori/FormalBears.FormalBears)

設定ロードに関わる実装コードを掲載します。

```php5

    protected function loadConfiguration(LoaderInterface $loader)
    {
    	// モジュール毎（個々のモジュールの設定）
        $loader->load($this->appMeta->configDir.'/modules/*'.$this->appMeta->configExtension, 'glob');

        foreach ($this->appMeta->contexts as $context) {
            if (is_dir($this->appMeta->configDir.'/modules/'.$context)) {
                // モジュール毎、コンテキスト別オーバーライド
                $loader->load($this->appMeta->configDir.'/modules/'.$context.'/**/*'.$this->appMeta->configExtension, 'glob');
            }
			// コンテキストグローバル
            $loader->load($this->appMeta->configDir.'/contexts/'.$context.$this->appMeta->configExtension, 'glob');
        }
    }

```

## 使ってみた感想

- デフォルトと環境毎の差分を書くやり方は実務ユースだと便利と感じました。
- （これはミートアップの方で話した `symfony/config` 利用に対する感想ですが、）これまで省いてきた設定のバリデーションをコンパイルステージで済ませられるようになった点が嬉しかったです。

## デメリット

コンフィグレーション言語機能無しの場合と比べると、下記がデメリットでしょうか。

- 直感的でなくなる（フレームワークの責務と規約が増える）
- 初心者には難しい
- FormalBears にコンテキストを読ませるのでグローバル値が1つ増える（ `$GLOBALS['context']` ）
- グラマー定義を書けるようになるまで少し慣れが要る
- グラマー定義のデバッグがひと手間

## 補足１：symfony/config の特性

（これもミートアップの方のトークテーマでしたが再録しておきます。）

Symfonyスタイルは、設定を、アプリケーション毎にバラバラに存在している個別的課題と見なすのではなく、ある１つの共通性を持った構造、それ自体を１つのドメインとして捉えています。設定の文脈、パラメータのグルーピング、データ構造、型といった問題の構造を、分かりやすく表現したいということです。

## 補足2：インフラストラクチャーに関連した設定

この記事では、インフラストラクチャーに関連した設定、環境変数の扱いついてふれられませんでいた。いずれまた別記事で紹介したいです。

## 終わりに

FormalBears のコンフィグレーション言語機能、ミニマムの使用例を紹介し、ソースコードを公開しました。
BEARは新しさを持ったフレームワークだけれど、既存の技術と組み合わせることによってもまた新たな可能性が拓かれうる、そんなことを私は最近学びました。


[^1]: [The Birth of FormalBears \- A new META for BEAR\.Sunday applications](https://www.slideshare.net/iteman/the-birth-of-formalbears)

[^2]: [symfony/config 統合例の紹介](https://www.slideshare.net/kumamidori/bearsunday-symfony-config)

[^3]: [公式マニュアル：アプリケーション](http://bearsunday.github.io/manuals/1.0/ja/application.html)

[^4]: [アプリケーション - Qiita ](http://bearsunday.github.io/manuals/1.0/ja/application.html)

[^5]: [公式マニュアル：モジュール - 環境](http://bearsunday.github.io/manuals/1.0/ja/module.html)