依存の注入

問題

Laravelフレームワークの土台となっているのは、強力なIoCコンテナです。フレームワークを本当に理解するには、このコンテナをしっかりと把握する必要があります。どんな開発であれ、IoCコンテナはソフトウェアデザインパターンの依存注入を実現する、簡単で便利なメカニズムであることに、留意しておく必要があります。依存注入を行うために、コンテナは必須ではありません。ですが、仕事を簡単にしてくれます。

最初に、どうして依存注入が有益なのかを解説しましょう。以下のクラスとメソッドを考えてください。

 1 class UserController extends BaseController {
 2 
 3 	public function getIndex()
 4 	{
 5 		$users = User::all();
 6 
 7 		return View::make('users.index', compact('users'));
 8 	}
 9 
10 }

このコードは簡潔ですが、データベースを操作せずに、テストすることができません。言い換えれば、Eloqunet ORMが、コントローラーときつく結合されています。このコントローラは、実際のデータベースを操作することを含め、Eloquent ORM全体を使用せずに、使用することも、テストすることもできません。さらに、このコードは、一般的に関心の分離(separation of concerns)と呼ばれる、ソフトウェアデザインの原則を守っていません。シンプルに言えば、コントローラは知りすぎています。コントローラーは、どこからデーターが来るのかを知る必要はなく、どうやってアクセスするかを分かっていれば良いのです。コントローラーはデーターがMySQL中に用意されていることを知る必要はなく、ただどこかに存在していることを知っていれば十分です。

関心の分離

全てのクラスには、責任を一つだけ、負わせなくてはなりません。そして、その責任はクラスにより、完全にカプセル化される必要があります。

ですから、データのアクセスレイヤーから、Web(コントローラー)レイヤーを完全に分離するのは、有益です。これにより、ストレージの実装を簡単に変更できるようになると同時に、テストも簡単に実行できるようになります。「Web」をあなたの「実際」のアプリケーションへのトランスポート層であると考えてください。

あなたのアプリケーションは、様々な接続ポートを持つ、モニターだと想像してみてください。そのモニターの機能に、HDMIや、VGA、DVIを通じてアクセスできるのです。インターネットは、アプリケーションへのケーブルに過ぎないと考えてください。モニターの機能の大部分は、ケーブルから独立しています。HTTPはあなたのアプリケーションの、転送メカニズムに過ぎません。ですから、転送メカニズム(コントローラー)をアプリケーションロジックで、取り散らかしたくはありませんね。そうすれば、APIやモバイルアプリケーションのような、どんなトランスポート層からも、アプリケーションロジックにアクセスできるようになります。

では、コントローラーとEloquent ORMを結合する代わりに、リポジトリークラスを挿入しましょう。

コントラクターの作成

最初にインターフェイス、それから対応する実装を定義しましょう。

 1 interface UserRepositoryInterface {
 2 
 3 	public function all();
 4 
 5 }
 6 
 7 class DbUserRepository implements UserRepositoryInterface {
 8 
 9 	public function all()
10 	{
11 		return User::all()->toArray();
12 	}
13 
14 }

次に、このインターフェイスの実装をコントローラーへ注入しましょう。

 1 class UserController extends BaseController {
 2 
 3 	public function __construct(UserRepositoryInterface $users)
 4 	{
 5 		$this->users = $users;
 6 	}
 7 
 8 	public function getIndex()
 9 	{
10 		$users = $this->users->all();
11 
12 		return View::make('users.index', compact('users'));
13 	}
14 
15 }

これでコントローラーは、どこにデーターが保存されているかを全く知りません。この場合、無知は至高の喜びです!データはMySQLからやってきても、MongoDBからでも、Redisからでも、構いません。コントローラーには違いが分かりません。もしくは、どうでもよいのです。ほんの小さな変更で、Webレイヤーをデーターレイヤから独立させて、テストできるようになり、同時にストレージの実装を簡単に変更できるようにもなるのです。

境界を尊重する

責任の境界を尊重することを覚えください。コントローラーとルートは、HTTPとアプリケーション間を仲介するように動作します。大きなアプリケーションを書く場合は、ドメインロジックとごちゃまぜにしてはいけません。

理解を深めるために、簡単なテストを書きましょう。最初に、リポジトリーをモックし、アプリケーションのIoCコンテナと結びつけます。それから、コントローラーがちゃんとリポジトリーを呼び出しているか、確認します。

 1 public function testIndexActionBindsUsersFromRepository()
 2 {
 3 	// Arrange...
 4 	$repository = Mockery::mock('UserRepositoryInterface');
 5 	$repository->shouldReceive('all')->once()->andReturn(array('foo'));
 6 	App::instance('UserRepositoryInterface', $repository);
 7 
 8 	// Act...
 9 	$response = $this->action('GET', 'UserController@getIndex');
10 
11 	// Assert...
12 	$this->assertResponseOk();
13 	$this->assertViewHas('users', array('foo'));
14 }

からかって(mock)いるんですか?

この例の中で、Mockeryモックライブラリーを使用しています。このライブラリーは、クラスをモックするために、クリーンで記述的なインターフェイスを提供しています。Mockeryは、Composerを使い、簡単にインストールできます。

先へ進む

更に深く理解するために、別の例を考えましょう。利用者がアカウント変更をおこなったら、皆さんも通知を受け取りたいと思うでしょう。2つのインターフェイス、もしくは契約を定義しましょう。これらの契約は、後ほど実装を変更するための柔軟性を与えてくれます。

1 interface BillerInterface {
2 	public function bill(array $user, $amount);
3 }
4 
5 interface BillingNotifierInterface {
6 	public function notify(array $user, $amount);
7 }

次に、BillerInterface契約を実装しましょう。

 1 class StripeBiller implements BillerInterface {
 2 
 3 	public function __construct(BillingNotifierInterface $notifier)
 4 	{
 5 		$this->notifier = $notifier;
 6 	}
 7 
 8 	public function bill(array $user, $amount)
 9 	{
10 		// Bill the user via Stripe...
11 
12 		$this->notifier->notify($user, $amount);
13 	}
14 
15 }

各クラスの責任を分離することにより、請求(billing)クラスに対する、様々な通知(notifier)の実装を簡単に注入できるようになります。例えば、SmsNotifierEmailNotifierを注入できます。請求クラスは通知の実装に、もう関心を持たず、ただ契約だけを知っていればよいのです。クラスが契約(インターフェイス)を守る限り、請求クラスは喜んで受け入れるでしょう。柔軟性を付け加えるだけでなく、さらに今では、BillingNotifierInterfaceのモックを注入することで、通知クラスから独立してテストができるようになりました。

インターフェイスでいきましょう

インターフェイスを書くのは、余計な仕事に思えるでしょうが、実際には開発をよりスピードアップしてくれます。実装を一行も書く前に、モックとアプリケーションの背景全体をテストするために、インターフェイスを使用してください。

では、どのように依存注入を_行う_のでしょう?とても簡単です。

1 $biller = new StripeBiller(new SmsNotifier);

これが、依存注入です。請求クラスがユーザーへの通知も関わる代わりに、その役割を通知クラスに譲りました。このシンプルな変更で、アプリケーションには素晴らしいことが起きます。クラスの責任が明確に線引きされたため、直ちにメンテナンスが行いやすくなりました。またテスト時に、依存しているクラスのモックを注入し、独立してテストできるようになったため、テストの行いやすさも急上昇です。

けれど、IoCコンテナはどうなんでしょうか?依存注入に必要なものではないのでしょうか?それは絶対に違います!これ以降の章で学習するように、コンテナは依存注入の管理を簡単にしてくれます。しかし、必須ではありません。この章の原則に従えば、コンテナが使える/使えないに関係なく、どんなプロジェクトにも、依存注入を実践できます。

Javaみたい?

PHPでインターフェイスを使用することへのよくある批判は、コードが”Java”のようになってしまうことです。彼らが話しているのは、コードが回りくどくなってしまうことです。あなたはインターフェイスを定義し、それから実装してください。ほんのちょっと、タイプの手間をかけるだけです。

小さくて簡単なアプリケーションであれば、この様な批判は多分合っているのでしょう。そのようなアプリケーションであれば、インターフェイスはほとんどの場合必要ありませんし、変更されることが無いと考えている実装に責任を負うなら問題ありません。あなたの実装に変更が起きないと確信しているのであれば、インターフェイスは必要ありません。宇宙飛行士のアーキテクチャが教えてくれているのは、「決して確信はできない」ということです。しかし、素直に捉えてみましょう。時々は、確信しているはずです。

大きなアプリケーションでは、インターフェイスは大いに役立ち、得られる柔軟性とテスタビリティーに比べれば、それをタイプする手間は大した事ありません。契約の実装を素早く変更できる設計は、あなたの上司に「おぉー」と言わせ、変更を簡単に受け付けられるコードをあなたに書かせてくれます。

最後に伝えておきましょう。この本が紹介するのは、とても「純粋」なアーキテクチャであることを覚えておいてください。もしあなたが、小さなアプリケーションに合わせて調整する必要があっても、罪悪感を覚えなくてよいのです。私達は皆、「幸せなコード(code happy)」を書こうとしているのを忘れないでください。自分で行なっていることを楽しめない、もしくはプログラムに罪悪感を感じるのであれば、前に戻り、再評価してみましょう。