Repository
Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.
- Domain ModelとData Mapper利用時
-
Data Mapperへの問い合わせを一元管理する間接層を1つ設けることは有用
- 【補】前節Query Objectもこの類
- 問い合わせロジックの重複の最小化
- ドメインオブジェクトのコレクションのようにふるまう
-
Repositoryのクライアントは、取得したいオブジェクトの仕様を宣言的に指定する
-
DataMapperの利用は隠蔽される
- 【補】利用しないこともある(後述)
-
-
パターンの意図
- オブジェクトがデータストア(DBとか)に永続化されていることの隠蔽
-
データストア操作の隠蔽
- オブジェクト指向なインタフェースの提供
-
【所感】Data Mapperとの違いを僕はこう解釈した
-
Data Mapper
-
利用側は永続化のことを知っている
- どう永続化されるかは知らない
- ので、
load
,save
といったメソッドがinterfaceに定義される
-
-
Repository
-
利用側は永続化のことを知らない
- 「メモリ上のドメインオブジェクトのコレクション」に見える
- ので、
matching(aCriteria)
,add(domainObject)
といったメソッドが定義される
-
-
How It Works
- 自分で作るというよりは、市販のO/Rマッパーについてくるのを見かけることが多いだろう
- Query Objectを自分で作ったならば、Repositoryパターンまでもう一息
-
複雑な裏側と裏腹に、インタフェースはただひとつ
matching(criteria)
: ドメインオブジェクトのコレクションを返す-
「1つだけ返す版」があってもいい
soleMatch(criteria)
findByXxId(id)
とか
-
ドメインオブジェクトがRepositoryに格納されているかどこかに取りに行くのか、利用側には知る由もない
- かといって
all()
で何十万レコードもデータフェッチしてしまうのは良くない
- かといって
- DataMapperの、用途特化の
findXx()
メソッドを、matching(criteria)
で置き換える -
Query Objectとの比較
-
共通点
- Criteriaを構築すること
-
相違点
-
Query Object
- SQLクエリの実行を意識させる
-
Repository
- Criteriaを満足するオブジェクトを良しなに取得する
-
-
-
中では本書であげた様々なパターンが絡み合っている
-
【補】具体的には…
-
Query Object
- CriteriaからSQL文の構築
- SQL文の実行をData Mapperに委譲
-
Metadata Mapping
- ドメインオブジェクトのフィールドとテーブルのカラムとのマッピング
-
Data Mapper
- SQL文の実行
- Record Setからドメインオブジェクトの構築
-
Identity Map
- ドメインオブジェクトのキャッシュ
-
-
-
Strategyでデータソースを隠蔽する
- 【所感】GoFのBridgeのほうが近くない?
- 【補】2019年現在、Repository自体のinterfaceをドメイン層に置くやり方のほうが流行ってる気がする
-
利用側のコードの可読性・明確さに貢献する
- HTTPリクエストを解釈し、入力にしたがってCriteriaを構築してRepositoryに渡すだけ
When to Use It
-
利用側でQuery Objectのセットアップコードが不要になる
- RDBを意識させない
-
データソースが複数になるとき本領発揮
-
例: 単体テスト時、in-memoryデータストア用のStrategyを注入する
- 高速化
-
fixtureが宣言的でわかりやすくなる
- setUp/tearDownで「DBにINSERT/DELETE文を発行する」必要がなくなる
- 例2: データソースがRDBではなく、外部APIからXMLやSOAPが降ってくる
-
Further Reading
- Evans読め
コード例
- 略
-
クラス図
-
特記事項
- 用途特化のfinderを生やしても良いよう(p.326)
-
Strategy Patternの適用
- 【所感】何度でも言うけどBridge Patternだろこれ
@startuml
package Domain {
class Person
class Criteria
abstract Repository{
- strategy RepositoryStrategy
+ matching(aCriteria Criteria): List<Person>
}
class PersonRepository
interface RepositoryStrategy {
+ matching(aCriteria Criteria): List<Person>
}
}
package DataSource {
class RelationalStrategy
class InMemoryStrategy
class QueryObject{
criteria: List<Criteria>
- toSql():string
+ addCriteria(aCriteria: Criteria)
+ execute(): List<Person>
}
class DataMapper{
+ findWhere(whereClause: string): List<Person>
}
class DB
}
PersonRepository -up-|> Repository
Person ..right..> PersonRepository
Person ..> Criteria : "<<create>>"
Repository o-right-> RepositoryStrategy : "<<delegate>>"
RelationalStrategy ..up..|> RepositoryStrategy
InMemoryStrategy ..up..|> RepositoryStrategy
RelationalStrategy ..> QueryObject : <<create>>
QueryObject .ri.> DataMapper
QueryObject o-- Criteria
DataMapper ..> Person : "<<create>>"
DataMapper .ri.> DB
@enduml
英語
-
benefactor
- 支援者
-
behind the scenes
- 舞台裏で