PoEAA Part 1 Chapter 3 Mapping to Relational Databases

PoEAAデザインパターン勉強メモ

前章の話

  • ドメイン層の設計の話

    • Transaction Script
    • Domain Model
    • Table Module
  • ドメイン層をさらに2層に分ける話

    • 上の層がサービス層

Mapping to Relational Databases

  • データソース層の役目: さまざなまインフラとのコミュニケーション
  • 特にDB
  • 特にRDB

    • メインフレームのISAMとかVSAMとかもあるけど、執筆時点で大多数はRDB
    • RDBの隆盛は、SQLの存在によるところが大きい

      • おおむね標準化されている
      • ベンダー拡張とかもあるけど…

Architectural Patterns

  • ドメインロジックからDBへの問い合わせかたは設計に左右されるという話

    • アーキテクチャの設計

      • 3層分け

        • プレゼンテーション
        • ドメイン
        • データソース
      • 分けない。SMART UI
    • ドメインロジックの設計

      • Transaction Script
      • Domain Model
      • Table Module
    • Domain Modelならデータソース層を精巧に作り込むことになる、とかそういう話
    • 後から変えるのが困難なので、選択時は注意
  • SQLでのDBアクセスとドメインロジックは分離しろ、という話

    • アプリケーション開発者はSQLについて詳しくないことが多い

      • ので、アプリケーションの開発言語でDBアクセスをカプセル化すべき
    • DBA: Database Administratorたちはクエリを最適化したりする

      • 分離すれば、彼らはドメインロジックを意識しないで済む
  • Gateway: SQLでのデータアクセスの責務

    • Gateway利用側はSQLを意識しない
  • Gatewayの分類

    • Row Data Gateway

      • 1行1オブジェクト
      • OOによくフィットする
    • Table Data Gateway

      • 1テーブル1オブジェクト
      • Record Setを取り扱う

        • GUIとか.NETのツールとかと相性がいい(前章の話)
  • 著者(Martin Fowler)は、シンプルなアプリケーションでもgatewayをつかう

    • =ドメインロジックの中でSQLを直接叩いたりしないってこと
  • Table Data GatewayをTable Moduleパターンと併用するのはすぐに思いつくこと
  • Table Data Gatewayはストアドプロシージャとも相性がいい

    • ストアドプロシージャの集まりがTable Data Gatewayクラスであるとみなせる
  • Domain Modelでドメイン層を設計する場合は、いずれのGatewayもイマイチ

    • 間接参照しすぎorしなさすぎ
  • 単純なアプリケーションでは、Domain Modelは複雑な構造にはならず、DBの構造とよく一致する

    • 1 Domain Modelクラスが1テーブルに対応
    • 1 Domain Modelオブジェクトが行に対応 (Row Data Gateway)
    • ビジネスロジックはさほど複雑ではない
    • この場合、Domain ModelオブジェクトにDB書き込み・読み出しの責務を負わせるのは理にかなっている
    • Active Record

      • Row Data Gateway + ドメインロジック

        • Transaction Scripts + Row Data Gatewayで始めて、
          コード重複をRow Data Gatewayに移していくとこの形になる
  • ビジネスロジックが複雑になっていくと、Active Recordは破綻する

    • Domain Modelクラスとテーブルとの1対1対応が成り立たなくなってくる
    • RDBは継承をサポートしないので、OOのデザインパターンを適用できない

      • Strategy Pattern
      • DBアクセスをモックしてテストする
  • Domain ModelとRDBのスキーマとを完全に分断したくなる

    • さもないと、一方の変更が他方に影響してしまう
    • Data Mapper中間層をつくろう

      • Domain ModelとRDBのスキーマとを完全に独立して変更できるようにする
  • Domain Modelを採用するなら、Gatewayで始めるのはオススメしない

    • ドメインロジックが単純で、Domain Modelがテーブルとよく一致するなら Active Record
    • 複雑ならData Mapper中間層をつくる
    • これらは排他的ではない

      • Domain Model - Data Mapper - Gateway - Table とかも可
  • 「テーブル」という言葉について

    • ビュー、クエリ、ストアドプロシージャに読み替えてもいい
  • ビューの更新でデータの一貫性が失われる問題

    • 同一の実表を参照している2つのビューに対して更新を行なった結果、二重更新になってしまう
    • 混乱するからやめようね
  • OOデータベースの話

    • オブジェクトをそのまま永続化できる
    • O/Rマッピングのコストがなくなる
    • 利点はあるが、リスクもあるからほとんど使われてない

      • RDBは枯れている
  • OOデータベースを使えないなら、O/Rマッパーの購入を検討したほうがいい

    • 本書でもData Mapperの実装方法についてあれこれ提示しているが、とても骨が折れる
    • 自前で作るより、何年もかけて鍛え上げられた市販のものを使ったほうがいい

      • 自前は、作るコストもメンテのコストもかかる
    • JDO in Javaとかある
  • 市販のO/Rマッパーを利用するにせよ、その設計について理解することはよいこと

    • マッピングのオプションがたくさんあり、その選定に有用
    • O/Rマッパーの導入は大きな前進だが、全部良しなにやってもらえるわけではない

The Behavioral Problem

  • O/Rマッパーにより、オブジェクトとテーブルのデータ構造のマッピングがなされる
  • 振る舞いがもう一つの柱
  • データ永続化の振る舞いをどうするか

    • レコード自身が保存・読み出しする: Active Record
  • 多数のレコードを永続化するとなると、一貫性の問題が浮上する

    • あるレコードの主キーを、別のレコードの外部キーが参照するような場合とか
    • 複数のオブシェクト読み出し中に、別プロセスでオブジェクトが書き換えられてはいけない
    • こういった同時性に関する問題は5章にて
  • 一貫性に関してはUnit of Workで解決

    • 一度に変更を加える
    • レコードのsave()メソッドを明示的に呼び出すのではなく、Unit of Workのcommit()を実行する
  • 同じレコードを2回読み出して、オブジェクトのコピーを生み出してはいけない

    • 別々にupdateすると混乱を招く

      • 1回目の変更が上書きされてしまう
    • Identity Mapで一意性を担保せよ

      • キャッシュみたいな感じ

        • ただし、パフォーマンス目的ではない。一貫性目的
  • Domain Modelを採用すると、1つのDomain Modelに複数のオブジェクトが紐づくことがある

    • 注文オブジェクトには顧客オブジェクトと商品オブジェクトが紐づく
    • 毎回必ずcustomersとproductsテーブルからもデータを取得するのは非効率

      • 空間
      • 時間
    • Lazy Load: 必要になったら読み込む

      • 【補】GoFでいうVirtual Proxyですね

Reading in Data

  • finderメソッド

    • find(id)
    • findFOrCustomer(customer)
  • どこに置く

    • テーブルオブジェクト

      • insertとかupdateとかと併用できる
    • 行オブジェクト

      • insertとかupdateとかと併用できない

        • insertやupdateはテーブルに対して行うものだから
  • 行オブジェクトのクラスメソッドにしたら?

    • 置換可能でなくなる

      • 【補】普通、static memberはoverrideできない
        というか、「静的」なので、動的ポリモーフィズムを適用できようはずもない

        • PHPとかはトクベツ
      • overrideしてテスト用のモックを作る、といったことができなくなる
  • 行オブジェクトにしたいときは、finderオブジェクトを作ると良い
  • finderメソッドやfinderオブジェクトのメソッドは、DBに対して行うもの

    • オブジェクトに対して行うものではない
    • オブジェクト100個に対応するデータを問い合わせたい場合、クエリを100回投げるのではなく、1回で取ってくるべき
  • データ取得においては、足りないよりも多いほうがマシ

    • 50件でいいのに200件取れちゃうクエリのほうが、50回クエリを投げるより良い
  • 複数テーブルに複数クエリを投げるのではなく、JOINして1クエリで取得する

    • Gateway
    • Data Mapper
    • RDBのJOINはせいぜい3-4個のテーブルの結合を想定して最適化されていることに留意せよ

      • それ以上となるとパフォーマンス問題が浮上する
  • パフォーマンスについては、推測するな計測せよ

Structural Mapping Patterns

  • O/Rマッピングというと、メモリ上のオブジェクトとDBの行との対応の意味で使われることが多い

    • Row Data Gateway
    • Active Record
    • Data Mapper
  • テーブルに対応するTable Data Gatewayは関係ないことが多い

Mapping Relationships

  • オブジェクトとRDBの関連とでは、リンクの取り回しが異なるよねという話
  • has-one, one-to-many関係

    • O: 所持側が所持される側の参照を保持する
    • R: 所持される側が所持する側を参照する(外部キー)

      • コレクションを直接扱えないため
  • Relationalにおいて、one-to-many関係を扱うと、many側がone側を参照する
  • 一意性問題が浮上する

    • manyがoneを参照しているため、同じoneを何度も問い合わせてコピーをメモリ上に積んでしまう
    • Integrity MapとForeign Key Mapping使え

      • Integrity Mapに登録がなければ…

        • 即DBに問い合わせる
        • Lazy Loadで必要読みする
      • 【疑問】Foreign Key Mapping

        • 具体的なパターンをまだ学んでないのでよくわからなかった
        • 言語機構のreferenceを使わず、int型のIDフィールドで参照を管理しよう、ってことかな?
    • コレクションの要素がそのコレクションからしか参照されないなら、Dependent Mappingで単純化できる
  • many-to-manyの関連の場合、連関テーブルが必要になる

    • Association Table Mapping
  • コレクションの順序の話

    • Object側は普通、順序のあるコレクションを使う

      • list
      • array
    • RDBが絡むと、順序つきデータを扱うのは難しくなる

      • 【補】そもそもRDBに順序は(表向き)ない
      • Object側で順序のないコレクションを使う?

        • set
        • テストしづらくなる
      • RDB側で毎回 ORDER BYする?

        • 高コスト
  • 参照の完全性の話

    • 環境が完全性チェックしてくれるなら使わない手はない
    • 環境の支援が受けられなければ、自分でチェックすることになる

      • 正しい順番でupdateされるか
  • Value Object

    • 単体でテーブルの行になるほど大きくないオブジェクト

      • date range(始点・終点、年月日)
      • money(金額・単位)
    • 他のオブジェクトにぶら下がる(Embedded Value)
    • 独自のセマンティクスがあり、パースのロジックなどがある

      • そこで一意性を担保すればいいので、Integrity Map不要
  • 巨大で複雑なコンポジットをRDBで扱うには

    • Serialized LOB

      • LOB: Large OBject

        • BLOB: Binrary Large OBject
        • CLOB: Character Large OBject
    • RDBは小さなオブジェクトの集合体を扱うのが苦手

      • 再帰構造とか
      • クエリをたくさん投げることになる
    • 複雑なコンポジットオブジェクトをXML等にシリアライズして、単一行の単一カラムに収める
    • やりすぎ注意

      • トランザクション機能つきの、ただのファイルシステムに成り果ててしまう
      • 【補】.txtを読み書きしてるのと変わらないよね、ということかと

Inheritance

  • RDBは再帰的なコンポジットを扱うのが苦手
  • 継承もサポートしてないよ、という話
  • どうする
Single Table Inheritance Concrete Table Inheritance Class Table Inheritance
概要 クラスツリー全体に対して1つのテーブル、和集合的なスキーマ 各具象クラスについて1つのテーブル、祖先との和集合的なスキーマ 各インタフェース、抽象クラス、具象クラスに対して1つのテーブル、1対1対応のスキーマ
メリット 変更が楽、JOIN不要 JOIN不要 インタフェース・クラスとテーブルとの対応関係が単純
デメリット 空カラムが無駄 変更の影響大。祖先インタフェース/クラスの変更が全テーブルに波及
キーが複数テーブルに分散するため、一貫性の確保が難しい
1つのオブジェクトのデータを取得するのに複数のテーブルのJOINが必要
  • 排他的なものではない

    • 混用すると複雑になるけどね
  • どれが一番良い、というものではない

    • 著者は初めにSingle Table Inheritanceを選ぶ傾向

      • 扱いやすい
      • リファクタリングの余地がある
  • interfaceの多重実装にも対応可能

Building the Mapping

  • O/Rマッピングするにあたって考えられるシチュエーション

    • スキーマを好きに選べる
    • スキーマがすでにあり、変えられない
    • スキーマがすでにあるが、変更の余地はある
  • スキーマを好きに選べて、ドメインロジックがさほど複雑でないなら:

    • ドメイン層

      • Transaction Script
      • Table Module
    • データソース層(SQLとかをカプセル化するとこ)

      • Row Data Gateway
      • Table Data Gateway
  • ドメイン層にDomain Modelを使うなら:

    • データソース層

      • Domain ModelとDBとを完全に分離したいなら:

        • Data Mapper
      • Domain Modelとテーブルとが同型であることが理にかなっているなら

        • Active Record
  • モデルを最初に構築してうまくいくのは、開発のイテレーションのサイクルが短いときだけなので注意

    • イテレーションのサイクルが長くて失敗するストーリー

      1. 6ヶ月かけてDBなしのDomain Modelを作りました
      2. さて永続化しましょう
      3. パフォーマンス出ませんでした
      4. リファクタリング大変です
    • イテレーションは長くとも6週、その度にデータソースを作り、フィードバックを反映せよ
  • スキーマがすでにあるなら:

    • ロジックが単純なら:

      • Row Data GatewayかTable Data Gatewayをつくり、その上にロジックを組み立てる
    • ロジックが複雑なら:

      • Domain Modelを作りたくなるが、既存のスキーマでうまくいくことはまずない

        • 【補】データソース層を精巧に作り込む必要があるから
      • ちょっとずつDomain ModelとData Mapperを育ててゆけ

Double Mapping

  • データの永続化先が複数ある場合

    • RDB
    • XMLメッセージ
    • CICSトランザクション

      • 【補】メインフレーム向けミドルウェア
  • 永続化先ごとにマッピング層を作るのはコードが多重化してよくない
  • まず論理的なデータストアにマッピングし、それを物理的なデータストアにマッピングせよ

    • 共通項を全社に

Using Metadata

  • 定型コードにまみれるので、メタデータを設けて汎化しましょうという話
<field name="customer"
       targetClass="Customer"
       dbColumn="custID"
       targetTable="custoemrs"
       lowerBound="1"
       upperBound="1"
       setter="loadCustomer />
  • 市販のO/Rマッパーはメタデータを使用する傾向にある
  • メタデータの理解のためには、まず手書きのハードコードの理解

Database Connections

  • connection

    • アプリケーションとDBとのインターフェースを務める
    • 【補】PHPのPDOとかのことかと
  • connectionの確立の切断は高コスト
  • なので、プールして使いまわしたい

    • 執筆時点で、大抵の環境でサポートされており、自前で書く必要はない
    • もしも自前で書くことになったら、そもそも必要かどうかから検証せよ

      • connectionの確立・切断は高速化してきている
      • 必要ないならプールしないに越したことはない
  • 環境によりconnectionのプールがサポートされている場合、connectionを生成しているのかプールから取ってきているのか意識する必要はない

    • 実際に何が起きているかによらずopen, closeと表現することにする
  • コストの高低によらず、connectionの管理はあくまでプログラマの責任

    • 使うときにopen
    • 使い終わったら忘れずにclose なる早で
  • connectionの管理方法

    • DBアクセスするメソッドに、引数として明示的に渡す

      • デメリット

        • 処理を委譲するときに引数のたらい回しになる

          • Registryパターンで回避可能
        • closeし忘れる
        • 全メソッドでcloseしてよいとも限らない

          • トランザクションの途中でcloseしてしまうと、トランザクションが失敗してrollback不可避
    • GCに紐づける

      • connectionを使用するオブジェクトが誰からも参照されなくなったらGCに回収される
      • デストラクタでconnectioをclose
      • デメリット

        • GCが走らないとconnectionがcloseされない
      • 著者の好むところではないが、永久にopenのままよりはマシ
    • トランザクションに紐づける

      • トランザクションはどのconnectionを利用しているか知っている必要がある
      • トランザクションの開始でconnection open
      • commitかrollbackでconnection close

        • 目に見えるので忘れにくい
      • トランザクションとconnectionの管理はUnit of Workでひとまとめにするとよい
  • 不変なデータの読み出し等、トランザクションを使わない場合でも、connectionのプールは有効

    • 一瞬しか使わないconnectionをいちいち確立・切断しない
  • disconnected Record Set

    • 利用時の一連の流れ

      1. connection openしてDBからデータ読み出し
      2. connection closeしてフィールド操作
      3. トランザクション開始・connection openしてDBへ書き込み
    • 2.の最中にDBが書き変わりうることを考慮すること(同時性問題)

      • 本書の後の方で扱う
  • 環境依存の部分多し

Some Miscellaneous Points

  • select *の問題

    • 新しい列を追加したら壊れる
    • 列番号を使用している場合、列順を追加したら壊れる
    • 単純なCRUDのテストが有効
  • プリコンパイルSQLは性能面で有効

    • prepared statementのこと
    • SQLクエリを組み立てるのに文字列結合使うな
  • コネクションをDBオブジェクトとして汎化し、置換可能にする

Further Reading

  • Brown and Whitenack
  • Ambler
  • Yoder
  • Keller and Coldwey

英語

  • comprise

    • Consist of; be made up of.
  • far-reaching

    • Having important and widely applicable effects or implications.
  • get at

    • Reach or gain access to (something)
  • feisty

    • Touchy and aggressive.
  • anecdotal

    • not necessarily true or reliable, because based on personal accounts rather than facts or research.
    • 要出典
  • make a dent

    • To make noticeable progress in a task or to consume a noticeable amount of something of which there is a large quantity.
  • get the hang of something

    • to learn how to do something, esp. when it is not simple or obvious:
    • コツを掴む
  • gotcha

    • A sudden unforeseen problem.
  • resilient

    • (of a substance or object) able to recoil or spring back into shape after bending, stretching, or being compressed.
  • on the whole

    • Taking everything into account; in general.