Patterns of Enterprise Application Architecture読書メモ(データソース編)

Martin FowlerのPatterns of Enterprise Application ArchitectureすなわちPoEAA、何年か前に原著をKindleで買って読もうとして序盤で挫折していた。最近またデータソースアーキテクチャについて翻訳版と原著を合わせて読んでいるので、整理のためメモを残しておく。

パターン

本書で紹介されるパターンはアプリケーションアーキテクチャから通貨の値オブジェクトまで、粒度もレイヤーも様々。今回はデータソースアーキテクチャパターンを理解するのに必要な箇所だけ調べた。

ドメインロジックパターン

  • トランザクションスクリプト
    • ほとんど構造化されてない書き下しなロジックのこと。ちょろいスクリプトならこれでもいいけど、ちょっと複雑になるとすぐに破綻する。
  • ドメインモデル
    • 業務上の概念をオブジェクト化して、それらを組み合わせて複雑なロジックを構成する。データマッパーサービスレイヤーパターンと組み合わせると良い。
  • テーブルモジュール
    • 上記二つの中間。RDBの1テーブルに対応するクラスでドメインロジックを構成するため、あまり複雑なアプリケーションには適合できないが、RDBなどとの相性は良い。
  • サービスレイヤ -

データソースアーキテクチャパターン

データベース操作のSQLとアプリケーションロジックの分離の仕方、ModelがControllerとデータベースの変換をどう扱うのかという話題。

  • テーブルデータゲートウェイ
  • 行データゲートウェイ
  • アクティブレコード
  • データマッパー

の4パターンが紹介されている。

テーブルデーゲートウェイ

P of EAA: Table Data Gateway

  • ひとつのデータベーステーブルがひとつのオブジェクトインスタンスになるゲートウェイ。テーブルへの操作(find,update,insert,delete)を備える。
  • Record Set(ジェネリックなデータ構造)やTable Module パターンとの併用に適している。
  • データベースの戻り値のRecord Setを返してもいいし、ドメインオブジェクトにマッピングして返してもいい。Record Setを返してデータ変換オブジェクトを用いれば、ユーザーが好きな方を選べて良いのではないか
  • ドメインモデルを使っている場合、テーブルデータゲートウェイドメインオブジェクトを返せるが、双方向の依存性が発生するのであんまり良くない
  • テーブルモジュール

// この例ではFindメソッドの戻り値にジェネリックなRecordSetを返却しているが、上の議論のように別にPersonオブジェクトを返しても問題はない。
class PersonGateway {
  Find(id): RecordSet
  FindWithLastNames(String): RecordSet
  Update(id, lastname,firstname, numberOfDependents)
  Insert(lastname, firstname, numberObDependents)
  Delete(id)
}
...
RecordSet r = personGateway.Find(1)
printf( r.Column("lastName") )
personGateway.Insert(2,"John","Doe",3)
personGateway.Update(2,"John","Doe",4)
personGateway.Delete(2)

行データゲートウェイ

P of EAA: Row Data Gateway

  • クエリ結果の行ひとつひとつがオブジェクトのインスタンスになる。ドメインオブジェクトがp.update()とか振る舞うのでオブジェクト指向感がある。
  • でもインメモリのオブジェクトにデータベースのアクセスコードが仕込まれる必要があるので、データベースとオブジェクトが密に結合してテストがやりにくかったりするデメリットがある。
  • トランザクションスクリプトに最適。
  • Findの動作をどこに定義するのかが問題になりがち
  • 行データゲートウェイに含まれるのはデータベースアクセスロジックだけ。ドメインロジックが含まれたらアクティブレコードパターンに進化する。

//static Findメソッドは別クラス(e.g. PersonFinder)に実装してもよい

class PersonGateway{
  lastname
  firstname
  numberOfDependents

  static Find(id): PersonGateway
  static FindWithLastName(String): PersonGateway[]

  insert()
  update()
  delete()
}
...
p1 = new PersonGateway()
p1.firstname = "John"
p1.lastname = "Doe"
p1.insert()

p = PersonGateway.Find(2)
p.numberOfDependents = 10
p.update()
p.delete()

アクティブレコード

P of EAA: Active Record

  • 行データゲートウェイのクラスにドメインロジックを追加したようなもの。
  • 行データゲートウェイテーブルデータゲートウェイドメインモデルとあまり相性が良くない。ドメインモデルであればアクティブレコードが向いてる(複雑なものでなければ)。
  • アクティブレコードクラスは以下のメソッドを持つ
    • SQLの結果行からアクティブレコードインスタンスを構築
    • 新しいインスタンスを構築
    • 静的なfindメソッドを使用してアクティブレコードオブジェクトを返却
    • データベースにアクティブレコードデータを挿入
    • フィールド取得、設定
    • ビジネスロジックの一部(バリデーションとか)
  • フィールドの型はテーブル内の各列と一致しなければならない。get/set時に他の効率のよい型への変換が認められる。
  • findメソッドは多くの場合静的に実装されるが、別に独立したクラスに分離してもよい。(テストもそのほうがしやすい)

class PersonGateway{
  lastname
  firstname
  numberOfDependents

  static find(id): PersonGateway
  static findWithLastName(String): PersonGateway[]

  insert()
  update()
  delete()

  getExemption()
  isFlaggedForAudit()
  getTaxableEarnings()  
}

データマッパー

P of EAA: Data Mapper

オブジェクトとデータベーステーブルは所詮別物(継承、コレクション、Joinの有無など)なので、1:1の対応でハンドリングするには限界がある。両者を変換するマッパがあれば複雑なドメインロジックと複雑なデータベーステーブルをハンドル出来るよ、的なパターン。

  • データマッパードメインオブジェクトとデータベーステーブルの間のデータの受け渡し(ロード/ストア)を責務とするソフトウェアレイヤ。ドメインモデルとデータベースを完全に分離し独立を保つ。ドメインオブジェクトはデータベースについて一切関知しなくてよくなる。
  • 全てのDB通信とマッピングデータマッパーが担うため、DBスキーマドメインオブジェクトそれぞれ独立して設計・開発が進められるメリット。
  • データマッパーがゲートウェイをラップすることはあり得る
  • 本書で紹介される「オブジェクトリレーショナルマッピングパターン」は、全てデータマッパーでオブジェクトとテーブルを接合させるためのストラテジ

class PersonMapper{
  insert(Person)
  update(Person)
  delete(Person)
}
class Person{
  lastName
  firstName
  numberOfDependents

  getExemption()
  isFlaggedForAudit()
  getTaxableEarnings()
}

データソースレイヤーアーキテクチャパターンの選び方

この本によると

  • ドメインモデルを使わない場合→ゲートウェイ系パターンでもなんとかなる
  • シンプルなドメインモデルを使う場合→アクティブレコードがよい
  • 複雑なドメインロジックを使う場合→データマッパーじゃないときつい

関連する基本パターン ゲートウェイマッパーの違い

ゲートウェイとマッパーはともに、ドメインオブジェクトと外部サービス(データベースなど)のインターフェイスとなる似たパターンだが、依存の方向性が逆方向になる。

ゲートウェイ

  • 外部サービスをラッピングして他のドメインオブジェクトが簡単に使えるようにする。
  • テストのためのサービススタブや、将来的に他のサービスに切り替えるためのポイントにもなる。間接化によって柔軟性を提供する。
  • PofEAA's Wiki - Gateway

マッパー

  • サブシステム間の通信を取り持つ分離レイヤー。サブシステム間の相互作用をハンドリングする。
  • PofEAA's Wiki - Mapper
    • 図のように、 マッパーが全ての関連サブシステムに依存している。サブシステムからマッパーへの依存はない。

その他のパターン

(今回は調べてない)

  • オブジェクトリレーショナル振る舞いパターン
  • オブジェクトリレーショナル構造パターン
  • オブジェクトリレーショナルメタデータマッピングパターン
  • Webプレゼンテーションパターン
  • 分散パターン
  • オフライン平行性パターン
  • セッションステートパターン
  • 基本パターン

エンタープライズ アプリケーションアーキテクチャパターン (Object Oriented Selection)

エンタープライズ アプリケーションアーキテクチャパターン (Object Oriented Selection)

Patterns of Enterprise Application Architecture

Patterns of Enterprise Application Architecture