PoEAA ch13 Repository

PoEAA勉強メモ

出典: 


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

    • 舞台裏で