InversifyJSで学ぶDI(Dependency Injection)

## はじめに
DIコンテナ自体は特に目新しい技術ではありません。JavaScript界隈ではAngularJS 1.xやRequireJS(AMD)等はそれ自体がDIの仕組みを内包したライブラリです。
しかし、これらのDIは若干無理やりな実装方法を取っていた感があります[^1][^2]。これはJavaScriptでメタデータやAOPを適切に扱う機能が不足していたことが背景にあると考えているのですが、ここ1, 2年で言語側の状況も変化してきています。
具体的にはTypeScript 1.5からDecoratorsがサポートされたり、ES 2015にてリフレクションの仕様が追加されたりと、よりスマートなDIコンテナを実装するための基盤が整いつつあります。

そこで今日は[InversifyJS](http://inversify.io/)という軽量JavaScript DIコンテナについて触れるとともに、最新のDI事情を見ていきたいと思います。

InversifyJSについては、少し前にgithubのtrendに上がっていたので気にはしていたんです。元々JavaをやってたときからDIコンテナ系の技術が好きというのもあります。
ただ、キャッチアップした当初のInversifyJSはAngularJS 1.xのような name-based なDI機構が提供されるのみであったため、あまり使いたいとは思っていませんでした。

そんなことをtwitterで愚痴ってたら、2.0.0-alpha.8でclass-based DI機構があれよあれよという間にmergeされたため、もう一度触ってみようと思った次第です。

<blockquote class="twitter-tweet" lang="ja"><a href="https://twitter.com/inversifyjs/status/711376730355847169"></a></blockquote>
<blockquote class="twitter-tweet" lang="ja"><a href="https://twitter.com/inversifyjs/status/716784487905017856"></a></blockquote>

InversifyJS自体はJavaScript, TypeScript両方をターゲットにしているDIコンテナですが、今日はclass-basedなDIの話を書く都合上、静的な型付言語であるTypeScriptを選択します。
「TypeScript におけるclass-based DI」と言えば[Angular2](https://angular.io://angular.io/)が有名ですが、DIのみ欲しいときにAngular 2は大仰に過ぎます。一方、InversifyJSは軽量というだけありはソースコードも充分読み易い量ですのでDIを学ぶには打ってつけだと思います。

## InversifyJSを触ってみる

まずはinstallです。reflect-metadataを忘れずに。

```sh
npm init -y
npm install inversify@2.0.0-alpha.8 reflect-metadata --save
```

続いてTypeScriptの設定をしましょう。

```sh
tsc --init
```

生成されたtsconfig.jsonをエディタで開き、experimentalDecorators, emitDecoratorMetadata, moduleResolutionを下記のように設定しておきます:

```json
{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "noImplicitAny": false,
        "sourceMap": false,
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true,
        "moduleResolution": "node"
    },
    "exclude": [
        "node_modules"
    ]
}
```

これでInversiffJSを利用する環境が整いました。ソースコードを書いていきます。

```ts:app.ts
///<reference path="node_modules/inversify/type_definitions/inversify/inversify.d.ts" />
import 'reflect-metadata';
import {injectable, Kernel} from 'inversify';

// 注入したい振る舞いの定義
@injectable()
abstract class AwesomeService {
    abstract greeting(): string;
}

// 振る舞いに対応した実装
@injectable()
class ConcreteAwesomeService extends AwesomeService {
    greeting() {
        return "It's a concrete awesome service!";
    }
}

// 注入される側のclass
@injectable()
class Main {
    constructor(private service: AwesomeService) {}
    run() {
        console.log(this.service.greeting());
    }
}

// DIコンテナの準備
const kernel = new Kernel();
kernel.bind<AwesomeService>(AwesomeService).to(ConcreteAwesomeService);
kernel.bind<Main>(Main).to(Main);

// コンテナからインスタンスを取得
const main: Main = kernel.get<Main>(Main);
main.run();
```

```sh:コンパイルと実行
tsc -p . && node app.js
```

コンソールに `It's a concrete awesome service!` が出力された筈です。

## ちょっとだけ解説

さすがに「DIが動きました!やったね！」でこのエントリを締めくくってしまうとあまりにもアホっぽいので、もう少しInversifyJSのDI機構について書いていこうと思います。

DI(Dependency Injection)って何なの？とかInversion of ControllだとかSOLIDとDIPだのについては、インターネッツに山のように情報が溢れていますのでここでは語りません。

先ほどのコード例では、`AwesomeService` , `ConcreteAwesomeService` , `Main` という3つのclassが登場しており、次の関係を満たしています:

1. `Main` という上位レイヤーは `AwesomeService` を利用しているが、`AwesomeService` の実体コードは気にしない
1. 実際に動作する `ConcreteAwesomeService` は 抽象型である `AwesomeService` に依存している

特筆すべきは、Main -> AwesomeServiceの依存関係が、型情報のみを用いて解決されているという点です。この部分の仕組みを追いかけてみます。

InversifyJSに限った話ではないですが、DIコンテナは「一般的に注入されるモノ」を管理するための辞書を内部的に保持しています(InversifyJSの場合、[Lookup class](https://github.com/inversify/InversifyJS/blob/master/src/kernel/lookup.ts) に辞書の実体が存在しています)。DIコンテナの利用者は、この辞書に対して値の登録を行う必要があります。

先ほどのInversifyJSのサンプルコードでは下記の部分が辞書への登録に該当します。

```ts
kernel.bind<AwesomeService>(AwesomeService).to(ConcreteAwesomeService);
kernel.bind<Main>(Main).to(Main);
```

* キー `AwesomeService` に対して、値 `ConcreteAwesomeService` を登録
* キー `Main` に対して値 `Main`を登録

抽象クラスのconstructor関数をキーとして、具象クラスのconstructor関数を値としたMapですね。

DIコンテナに登録された Main classのインスタンスを利用する場合は、`kernel.get(Main)` とすることで辞書に登録した情報を元にDIコンテナがconstructorを解決してくれます。

```ts
@injectable()
class Main {
    constructor(private service: AwesomeService) {}
    /* 中略 */
}
```

Main classのconstructorには `AwesomeService` 型の推移依存が記載されており、`AwesomeService` 型は 先述の`kernel.bind(AwesomeService)` にてkernelに登録済みです。
やはり `kernel.get(AwesomeService)` を実行することで `ConcreteAwesomeService` のインスタンスが取得可能です。このように、constructor引数の型をキーとして再帰的にkernelからのインスタンス取得を繰り返していくことで、最終的にMain classのインスタンスが取得できるわけです。

ここで「`service: AwesomeService` の型情報はTypeScriptのソース上は存在していても、.jsにコンパイルされたら消えてしまうんじゃない？」という疑問が湧いてきます。

答えはNoです。Main classはTypeScriptによってコンパイル(desugar)したJavaScriptは次のes6コードと等価です(可読性のために実際のコンパイル結果とは異なるコードを記載):

```js:es6で書いた場合のMain
class Main {
    constructor(private service) {}
    run() {
        console.log(this.service.greeting());
    }
}
Reflect.metadata('design:paramtypes', [AwesomeService])(Main);
injectable()(Main);
```

末尾2行がポイントです:

```js
Reflect.metadata('design:paramtypes', [AwesomeService])(Main);
injectable()(Main);
```

* `Reflect.metadata`, `injectable` はclassのconstructor関数を引数にとる関数を返します。これは Class Decorator[^3]の形式ですので、TypeScriptのコード上は`@inject()`のように呼びだす事ができたということです。tsconfig.jsonに`experimentalDecorators` を設定したのはこのためです。
* `Reflect.metadata` は reflect-metadata モジュールの機能です。'design:paramtypes' という特殊なキーが、「関数引数の型情報」を意味しています。
 * `injectable`は内部で`Reflect.getMetadata` を呼び出し、'design:paramtypes' から型情報を回収し、コンテナへ連携しています[^4]。
* `Reflect.metadata` の行はClass Decoratorが利用された場合にTypeScriptが付与します(tsconfig.jsonに `emitDecoratorMetadata` が設定されている場合のみ)。

ReflectとDecoratorsが大活躍です[^5]。冒頭の環境構築時の説明では何も触れませんでしたが、`npm install reflect-metadata` としたり、tsconfig.jsonに設定を追記していたのは、この2行の為だったということです。
試しにtsconfig.jsonの`emitDecoratorMetadata`をfalseに設定してみて再度実行してみて下さい。`service.greeting()`の呼び出しでコケるはずです。

なお、`emitDecoratorMetadata` が有効であっても、constructor引数の型がinterfaceで定義されている場合はInversifyJSのDIは動作しません。
というより、`kernel.bind` にてinterfaceをbindすることすら出来ません。どうしても引数の型にinterfaceを使った状態でDIを行いたければ、[Symbol を使うなり](https://github.com/inversify/InversifyJS/#support-for-symbols)の解決方法が用意されています。interafaceに固有のSymbolを用意する必要があったりと若干手間です。

## おわりに
今回のエントリでは、InversifyJSによるclass baseなDIの基本を述べました。またclass base DIを実現するために必要な「どのようにDIコンテナへ型情報を伝えるか」の部分を解説しました。TypeScriptでAngular2を使う場合も reflect-metadata とtsconfigの`experimentalDecorators`, `emitDecoratorMetadata`は必須になるのですが、DIコンテナの実装は違えど、これらの設定が必要になる理由は全く一緒です。

InversifyJSの詳細な使い方については特に詳しくは触れませんでしたが、APIから察するにJavaの軽量DIコンテナである[google/guice](https://github.com/google/guice/wiki/GettingStarted)の影響が色濃く見て取れます。`kernel.bind(...).to(...).inSingletonScope()`のようにメソッドチェーンでinterface絞っていく所とかそっくりです。
DIの典型的なユースケースは「特定のserviceについてテスト時はモックに差し替える」等がありますが、kernelのbind方法を押えておけば、あとはmochaやjasmineのようなテスティングフレームワークと組み合わせるだけで簡単に実現できると思います。

[^1]: AngularJS 1.xにおけるDI機構の裏側は [AngularJSのDIの仕組み、minify対策は覚えておこう！](http://qiita.com/kawaz/items/363f430d21ec729f1b7d) が詳しいです
[^2]: [Dependency injection in JavaScript](http://krasimirtsonev.com/blog/article/Dependency-injection-in-JavaScript) にRequireJS, AngularJSのDI実装方法の概要が紹介されています
[^3]: class-based DIとかname-based DIと呼び分けていますが、このキーワードでググっても殆ど何もヒットしません。今回の記事では型情報を用いてDIを行う手法をclass-based DI, 名前情報を用いてDIを行う手法をname-based DIと呼ぶことにします
[^4]: TypeScriptのClass Decoratorについては[TypeScriptのDecoratorメモ](http://qiita.com/Quramy/items/e3a43bb1734b8a7331e8)にも記事を投稿しています。
[^5]: [https://github.com/inversify/InversifyJS/blob/master/src%2Fannotation%2Finjectable.ts](https://github.com/inversify/InversifyJS/blob/master/src%2Fannotation%2Finjectable.ts)
[^6]: [Class Decorators](https://github.com/wycats/javascript-decorators/blob/master/README.md), [Reflect Decorators](https://github.com/rbuckton/ReflectDecorators)ともにECMA Scriptの仕様として固まっている訳ではありませんが、TypeScriptで先取り実装されているので、まぁ乗っかってもいいんじゃない？って個人的に思ってます。