勉強日記

チラ裏

SQL Antipatterns ch25 Magic Beans

pragprog.com


Magic Beans

Decouple your model from your tables.

  • フレームワークのコードと密結合してテストがままならない
    • DBをリロードしなければならない
  • 1つの機能を追加するだけなのに甚大な工数がかかる

Objective: Simplify Models in MVC

  • MVC
    • Controller
      • ユーザ入力を受け取る
      • レスポンスを返却する
      • 適切なモデルに処理を委譲する
    • Model
    • View
      • UI上で情報を表示
  • VとCはわかりやすい
  • Mは曖昧
  • ソフトウェア設計の複雑性を減らすために、「モデルとは何たるか」を一般化・単純化したいという強い欲求がある
  • 「データアクセスオブジェクトにすぎない」という過度な単純化に陥る

Antipattern: the Model Is an Active Record

  • 単純なアプリケーションでは
    • モデルに多くのカスタムロジックを積む必要がない
    • 単一のテーブルのカラムとモデルオブジェクトとが素直に一致する
    • PoEAAのActive Record
      • 【補】ロジック積まないならRow Data Gatewayじゃない?
      • RoRが有名にした
      • DAO
  • すべてのモデルをActive Recordにしてしまうのはアンチパターン
    • 【補】原典のPoEAAでも「複雑なドメインロジックには対応できない」と言っている
    • 金槌しか持っていないと、すべてのものが釘に見えてしまう
  • ジャックと豆の木
    • CoCへの批判
      • ジャックは寝ている間に豆の木が天高く育つことを知っている
      • ジャックの物語ではうまく働く
      • しかし他の物語でもいつもうまくいくとは限らない

コラム: Leaky Abstractions

  • オブジェクトでデータ永続化を隠蔽・汎化しようとする
  • しかし、JOINとかGROUP BYとか、結局RDBを意識しないといけない

Active Record Couples Models to the Schema

  • そのまんま

Active Record Exposes CRUD Functions

  • ビジネスルールをクライアントコードに強制できない
    • Active Recordを継承すると、親のfind()save()はクライアントコードに見えてしまう
    • 「レコード保存時にメール送信する」ビジネスロジックを実現するassignUser()メソッドを作ったとする
    • save()生呼びされると、メール送信が行われない

Active Record Encourages an Anemic Domain Model

  • 「貧血症」ってやつ
  • DAOがドメインロジックを持たないため、クライアントコード側にドメインロジックが漏出する
    • Controllerとか

Unit Testing Magic Beans Is Hard

  • MVC各層のテストが困難に
    • Model
      • DBから切り離してテストできない
      • DBのfixtureは遅くエラーが生じやすい
    • View
      • 同様
    • Controller
      • Modelが単なるDAOだと、ドメインロジックがControllerに漏出
      • 多くのControllerでコード重複が生ずる
        • ので、テストも重複する
      • ドメインロジックをテストするためにHTTPリクエストをモックしないといけない
        • セットアップコードが大きくなる
        • 遅い

How to Recognize the Antipattern

  • こんなのが聞こえてきたら、Magic Beansアンチパターンの証
    • 「モデルにSQLクエリを渡すには?」
      • 「モデル」を「DBアクセスクラス」の意で使っている
      • データアクセスを隠蔽できてない
    • 「複雑なモデル問い合わせをすべてのコントローラにコピペすべきか、1つの抽象コントローラに持っていくべきか」
      • どっちもNO
      • モデルに閉じ込めろ
        • DRY原則
        • モデル利用方法がシンプルに

Legitimate Uses of the Antipattern

  • シンプルなCRUDならどうぞ
    • Active Recordパターンそれ自体はわるくない
  • プロトタイプ開発
    • 開発速い
    • 「プロトタイプモード」で書いたコードは技術的負債であることに留意せよ

Solution: the Model Has an Active Record

  • 継承ではなく集約・委譲

Grasping the Model

Information Expert

  • ある操作が複数DBにまたがるような場合
  • Active Record (単一DBに対応)を複数集約するクラスを用意せよ

Creator

  • DAOを集約するクラスがDAOを構築するように
    • 【補】今日びはサービスコンテナから取り出したりする
      • PoEAAのRegistryが近い

Low Coupling

  • アプリケーションの要件それ自体は単純化できない
  • どこかは複雑にならざるを得ない
  • 複雑な部分を疎に切り離すことが肝要
  • 【所感】関数型プログラミングで「副作用」を隔離するのと似た考え

High Cohesion

  • ModelとDAOとを切り離す利点
    • assignUser()のようなメソッドを提供できる
      • Controllerから利用したいのはこういうメソッド
      • find()save()といったメソッドはアプリケーションの要件を表していない
    • 複数のモデルから同一のDAOを利用できる
      • テーブル操作単位でModelをまとめるよりも凝集度において優れる

Putting the Domain Model into Action

  • Domain ModelにDAOを集約させる効能
    • クラスの関連がすっきりする
      • 凝集度が高まったため
    • ModelのインタフェースとDB操作とを切り離すことで、クライアントコード(Controller)がすっきりする
      • Controllerからは、「ある操作がどのテーブルが関わっているか」意識しない
      • DBクエリも意識しない
    • Active Record (ORM)で記述できない複雑なSQLクエリが必要になったら、Model内に閉じることができる
      • Controllerにまで漏出しない

Testing Plain Objects

  • Model
    • DAOのstubを注入し、DBと切り離してテストできる
    • ドメインロジックをControllerから切り離したため、HTTPリクエストのモックなしでテストできる
  • Controller
    • HTTPリクエストのモックは必要
    • だが、Controller自体薄くなったためテストは楽

Getting Down to Earth


英語

  • per se
    • それ自体に