勉強日記

チラ裏

PoEAA ch11 Unit of Work

martinfowler.com


Unit of Work

Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.

  • DBへの変更をともなうオブジェクトの変更を追跡する
    • 生成
    • 削除
    • 更新
  • オブジェクトの変更のたびにDBアクセスするのは良くない
    • 小さなDBリモートコールを何度も行うことになり、遅い
    • 整合性のためには、システムトランザクションを長時間開いていなければならず、現実的でない
    • inconsistent readがおきる
    • 【所感】ビジネス「トランザクション」とは…
  • ビジネストランザクションのcommit時にDBへの変更をまとめて反映する

How It Works

  • オブジェクトの生成、更新、削除時にはUnit of Workに登録する
  • inconsistent readを防ぐためにも使用する
  • Unit of Workによるビジネストランザクションのcommit
    1. システムトランザクションを開始する
    2. 並列制御
      • Pessimistic Offline Lock
      • Optimistic Offline Lock
    3. DBに変更を書き込む
  • オブジェクトの変更の追跡様式にはいくつか種類がある
    • caller registration
    • object registration
    • unit of work controller
  • caller registration
    • caller = オブジェクトを生成するクライアントコード
    • クライアントコードが忘れずにUnit of Workへの登録を呼び出す
      • 新規生成時はregisterNew
      • 読み込み時はregisterClean
      • 更新時はregisterDirty
      • 削除時はregisterDelete
    • メリデメ
      • メリ
        • 柔軟
          • 「あえて登録しない」ことで、メモリ上のオブジェクトの変更をDBに反映しないこともできる
            • もっとも、これは明示的にcloneを作るべき
      • デメ
        • クライアントコード側での登録し忘れがおきる
  • object registration
    • 生成されるオブジェクトが、自身をUnit of Workに登録する
    • オブジェクトはUnit of Workを知っている必要がある
      • どうやって渡す
        • メソッドやctorの引数で引き回す
        • thread-scopedのRegistry等、どこからでもアクセスできる場所に置いておく
    • メリデメ
      • メリ
        • クライアントコード側で、Unit of Workへの登録を忘れることはなくなる
      • デメ
        • オブジェクトの開発者は依然としてUnit of Workへの登録を忘れずに実装しなければならない
          • コード生成やAOPの使いどころ
  • unit of work controller
    • DBアクセスのControllerとしてのUnit of Work
    • DBからデータを読み込むときにはUnit of Workを通す
      • クライアントコードは、空のオブジェクトを生成し、Unit of Workに渡してデータを読み込ませる
      • Unit of Workはオブジェクトをcopyして返す
        • UPDATE時には控えのcopyと照合して、実際に変更されたフィールドのみを更新できる
    • メリデメ
      • メリ
        • Unit of Workへの登録忘れがない
      • デメ
        • copyのオーバヘッド
    • 変更したオブジェクトのみcopyすることでオーバヘッドを減らせる
      • Unit of Workへの登録は必要になる
        • 【所感】Copy-on-Writeで回避できないかな?
      • updateよりもreadが多い場合に有効
  • オブジェクトの生成について、caller registrationの検討
    • オブジェクトを一時的に生成はするがDBには登録したくないということはある
      • 【補】LaravelのEloquentのmake()みたいな気持ち
    • 例: ドメインオブジェクトのテスト
      • DBへの書き込みがないほうが高速
    • DBへの書き込みからどうやって切り離す
      • caller registration方式で、Unit of Workに登録しない
        • Unit of Workへの登録を行わないコンストラクタを生やす
        • 何もしないUnit of Workに登録する
          • Special Case
  • 更新順
    • 参照整合性チェックにより、DBの更新順によっては更新失敗する
    • システムトランザクションRDBMSがよろしくやってくれるならそれを使え
    • そうでない場合、更新順はUnit of Workで管理するのが自然
      • 本書の守備範囲外
      • トポロジカルソートが鍵
        • 【補】有効非巡回グラフのノードのソート
  • デッドロック回避
    • 全てのシステムトランザクションで同じ順序でロックを取得すること
    • この管理もUnit of Workが適所
  • batch update
    • 1回のリモートコールで複数のSQLを実行する
    • JDBC等のプラットフォームでサポートされている
    • プラットフォームのサポートがない場合はUnit of Workで模すとよい
  • RDB以外のトランザクショナルリソースにいてもUnit of Workは適用可能

When to Use It

  • 根本は、メモリ上オブジェクトとDB上データとの同期
  • すべてをシステムトランザクションの中で行えるならば、自分が変更したオブジェクトだけを気にすればいい
  • 【補】しかし、複数のリクエスト/レスポンスを含むセッション = ビジネストランザクションを丸ごとシステムトランザクションで包むのは現実的でない
  • 【補】そのため、自前でビジネストランザクションを管理する必要がある
    • 他のセッションによるDBへの変更を考慮しなければならない
  • ビジネストランザクションのcommit時に、変更をまとめてDBに反映するUnit of Work以外の方法
    • Transaction Scriptの中で、変数で管理する
      • すぐ手に負えなくなる
    • オブジェクトにdirty flagをもたせる
      • オブジェクトグラフを走査して、dirty flagの立っているものをDBに反映する
      • Domain Model採用時はオブジェクトグラフを走査するのは難しい
        • ドメインオブジェクトのネットワークは単純なコレクションや木ではないため
  • Unit of Workの特長は、オブジェクトの変更を一元管理すること
    • 【補】コレクションとして水平に持つので走査もしやすい
  • Optimistic Offline LockやPesimistic Offline Lockを適用する土台にもなる