リポジトリパターン。 基本と説明

リポジトリは通常、多くの場合安全または保存のために、保管場所を指します。

-ウィキペディア



これは、ウィキペディアがリポジトリを説明する方法です。 たまたま私たちが扱っている他の俗語とは異なり、この用語はその本質を完璧に伝えています。 リポジトリは、特定のタイプのエンティティのコレクションストレージの概念です。



コレクションとしてのリポジトリ



おそらくリポジトリの最も重要な違いは、それらがオブジェクトのコレクションであることです。 データベースストレージやキャッシュ、またはその他の技術的な問題の解決策については説明しません。 リポジトリはコレクションを表します。 これらのコレクションを保存する方法は、実装の詳細にすぎません。



この問題を明確にしたい。 リポジトリはコレクションです。 エンティティを含み、アプリケーションの要件に応じて結果をフィルタリングして返すことができるコレクション。 これらのオブジェクトを保存する場所と方法は、実装の詳細です。



PHPの世界では、プロセスの終了で終了する要求/応答サイクルに慣れています。 この時点で、外部から来て生き残れなかったものはすべて永遠に消えてしまいます。 したがって、すべてのプラットフォームがそのように機能するわけではありません。



リポジトリがどのように機能するかを理解する良い方法は、アプリケーションを常に実行し続けることです。この場合、すべてのオブジェクトがメモリに残ります。 この実験での重大な失敗の可能性とそれらへの反応は無視できます。 Member



MemberRepository



リポジトリのシングルトンインスタンスがあるとします。



次に、新しいMember



オブジェクトを作成し、リポジトリに追加します。 後で、リポジトリに格納されているすべての要素をリポジトリに要求するため、内部にこのオブジェクトを含むコレクションを取得します。 IDで特定のオブジェクトを取得することもできますが、それも可能です。 リポジトリ内では、これらのオブジェクトが配列に格納されていること、さらにはコレクションオブジェクトに格納されていることを想像するのは非常に簡単です。



簡単に言えば、リポジトリは、エンティティを保存およびフィルタリングするために何度も使用する特別な種類の堅牢なコレクションです。



リポジトリとの相互作用



Member



エンティティを作成しているとします。 オブジェクトを必要な状態にすると、リクエストが終了し、オブジェクトが消えます。 ユーザーがアプリケーションにログインしようとしていますが、できません。 明らかに、このオブジェクトをアプリケーションの他の部分で利用できるようにする必要があります。



 $member = Member::register($email, $password); $memberRepository->save($member);
      
      





これで、後でオブジェクトにアクセスできます。 このようなもの:



 $member = $memberRepository->findByEmail($email); // or $members = $memberRepository->getAll();
      
      





アプリケーションのある部分にオブジェクトを保存し、別の部分からオブジェクトを取得できます。



リポジトリはエンティティを作成する必要がありますか?



そのような例に出くわすかもしれません:



 $member = $memberRepository->create($email, $password);
      
      





私はこれを支持して多くの議論を引用しましたが、私はそのようなアプローチにはまったく興味がありません。



まず第一に、リポジトリはコレクションです。 コレクションがコレクションとファクトリーである理由がわかりません。 「それを処理する方が便利場合、そのようなアクションにハンドラーを配置してみませんか?



私の意見では、これはアンチパターンです。 オブジェクトを作成する方法と理由をMember



クラスに独自に理解させないのはなぜですか、または、より複雑なオブジェクトを作成するために別のファクトリーを作成しないのはなぜですか



リポジトリを単純なコレクションとして扱う場合、追加機能をロードする必要はありません。 ファクトリーのように動作するコレクションクラスは必要ありません。



リポジトリを使用する利点は何ですか?



リポジトリの主な利点は、エンティティコレクションの抽象ストレージエンジンです。



MemberRepository



インターフェースを提供することにより、データを保存する方法と場所を決定する開発者の手をMemberRepository



ます。



 interface MemberRepository { public function save(Member $member); public function getAll(); public function findById(MemberId $memberId); }
      
      







 class ArrayMemberRepository implements MemberRepository { private $members = []; public function save(Member $member) { $this->members[(string)$member->getId()] = $member; } public function getAll() { return $this->members; } public function findById(MemberId $memberId) { if (isset($this->members[(string)$memberId])) { return $this->members[(string)$memberId]; } } }
      
      







 class RedisMemberRepository implements MemberRepository { public function save(Member $member) { // ... } // you get the point }
      
      





したがって、ほとんどのアプリケーションは、MemberRepositoryの抽象的な概念のみを知っており、その使用は実際の実装から分離できます。 これは非常に自由です。



リポジトリに関連するもの:ドメインまたはアプリケーションサービスレイヤー



興味深い質問があります。 最初に、 Application Service Layer



が、データベースの整合性などの特定のアプリケーション実装の詳細、およびインターネットプロトコル(電子メールの送信、API)などのさまざまな実装を担当するマルチレベルアーキテクチャであることを定義しましょう。



Domain Layer



という用語は、ビジネスルールとビジネスロジックを担当するマルチレベルアーキテクチャの層として定義します。



リポジトリはこのアプローチでどこに行きますか?



例を見てみましょう。 これが以前に書かれたコードです。



 class ArrayMemberRepository implements MemberRepository { private $members = []; public function save(Member $member) { $this->members[(string) $member->getId()] = $member; } public function getAll() { return $this->members; } public function findById(MemberId $memberId) { if (isset($this->members[(string)$memberId])) { return $this->members[(string)$memberId]; } } }
      
      





この例では、多くの実装の詳細が表示されます。 それらは間違いなくアプリケーション層の一部であるべきです。



さて、このクラスからすべての実装の詳細を削除しましょう...



 class ArrayMemberRepository implements MemberRepository { public function save(Member $member) { } public function getAll() { } public function findById(MemberId $memberId) { } }
      
      





うーん...見慣れた感じになり始めます...何を忘れましたか?



おそらく、結果のコードはこれを思い出させますか?



 interface MemberRepository { public function save(Member $member); public function getAll(); public function findById(MemberId $memberId); }
      
      





これは、インターフェイスがレイヤー境界にあることを意味します。 実際、ドメイン固有の概念が含まれている場合がありますが、実装自体はこれを行うべきではありません。



リポジトリインターフェイスはドメイン層に属します。 実装はアプリケーション層に関連しています。 これは、サービスレイヤーに依存することなく、ドメインレイヤーレベルでアーキテクチャを自由に構築できることを意味します。



データウェアハウスを変更する自由



オブジェクト指向設計の概念について誰かが話しているのを聞くたびに、 「...そして将来、データストレージの実装を別のものに変更する機会があります...」



私の意見では、これは完全に真実ではありません...これは非常に悪い議論であるとさえ言うでしょう。 リポジトリの概念を説明する最大の問題は、質問がすぐに「あなたは本当にこれをやりたいですか?」 このような質問がリポジトリパターンの使用に影響することは望ましくありません。



適切に設計されたオブジェクト指向アプリケーションは、自動的に与えられた利点に適合します。 OOPの中心概念はカプセル化です。 APIへのアクセスを提供し、実装を隠すことができます。



結局のところ、実際には1つのORMから別のORMに、またはその逆に切り替えません。 しかし、これを行いたい場合でも、少なくともあなたにはそうする機会があります。 ただし、テスト時には、リポジトリ実装を置き換えることは大きなプラスになります。



リポジトリパターンを使用したテスト



まあ、すべてが簡単です。 メンバーの登録などを処理するオブジェクトがあると仮定しましょう...



 class RegisterMemberHandler { private $members; public function __construct(MemberRepository $members) { $this->members = $members; } public function handle(RegisterMember $command) { $member = Member::register($command->email, $command->password); $this->members->save($member); } }
      
      





次の操作中に、 DoctrineMemberRepository



インスタンスを取得できます。 ただし、テスト中に、ArrayMemberRepositoryのインスタンスに簡単に置き換えることができます。 どちらも同じインターフェースを実装しています。



簡単なテスト例は次のようになります...



 $repo = new ArrayMemberRepository; $handler = new RegisterMemberHandler($repo); $request = $this->createRequest(['email' => 'bob@bob.com', 'password' => 'angelofdestruction']); $handler->handle(RegisterMember::usingForm($request)); AssertCount(1, $repo->findByEmail('bob@bob.com'));
      
      





この例では、ハンドラーをテストしています。 データベース(または他の場所)にリポジトリデータを格納する正確性をチェックする必要はありません。 このオブジェクトの特定の動作をテストします。フォームデータに基づいてユーザーを登録し、それらをリポジトリに転送します。



収集またはステータス



Implementing Domain-Driven Designブックで、Vaughn Vernonはリポジトリのタイプを区別しています。 コレクション指向のリポジトリ(元:コレクション指向のリポジトリ)の考え方は、 リポジトリとの連携がアレイと同様にメモリ内にあるということです。 状態指向のリポジトリ(オリジナル-永続指向のリポジトリ)には、より深く考え抜かれたストレージシステムがあるという考えが含まれています。 実際、違いは名前だけです。



 // collection-oriented $memberRepository->add($member); // vs persistence-oriented $memberRepository->save($member);
      
      





これは私の意見に過ぎず、これまでのところリポジトリの使用に関してはこれにこだわっていることに注意してください。 しかし、気が変わるかもしれないと警告したいと思います。 最後に、他のコレクションオブジェクトと同じ責任を持つオブジェクトのコレクションとしてそれらに注目します。



追加情報



everzetは Githubリポジトリプロジェクトを作成しまし 。これは一見の価値があります。 内部には、メモリとファイル内のストレージを操作する例があります。



まとめ



私は信じています...

  1. ...リポジトリにオブジェクトのコレクションとして機能する特異なタスクを与えることが重要です。
  2. ...リポジトリを使用してオブジェクトの新しいインスタンスを作成しないでください。
  3. ...ある技術から別の技術に移行する方法としてリポジトリを使用することは避けるべきです。なぜなら、それらには拒否するのが難しい多くの利点があるからです。


将来、デコレータを使用した結果のキャッシュ、基準パターンを使用したクエリ、多数のオブジェクトのバッチ操作を処理する際のリポジトリの役割など、リポジトリに関する記事をさらに書く予定です。



質問がある場合、または自分の意見が私の意見と異なる場合は、下にコメントを書いてください。



いつものように、私は記事を更新して現在の意見と同期させるつもりです。



All Articles