マジックビーンズ

25
マジックビーンズ Twitter: @a_suenami Github: a-suenami id: a_suenami

description

SQLアンチパターン読書会でマジックビーンズについて発表しました。

Transcript of マジックビーンズ

Page 1: マジックビーンズ

マジックビーンズ

Twitter: @a_suenami Github: a-suenami

id: a_suenami

Page 2: マジックビーンズ

自己紹介• 末並晃(すえなみあきら)

• @a_suenami

• 髪切りました / 痩せました

• RubyとかPHPとかJavaScriptとか

• いわゆる”Web系”エンジニア

• どちらかというと自然キー/複合キー容認派

• どちらかというとNULL撲滅したい派

Page 3: マジックビーンズ

MVC アーキテクチャにおいて、

M ( モデル ) の設計や実装を単純化するために、 すべてのモデルをアクティブレコードの 継承クラスにするというアンチパターン

マジックビーンズとは

Page 4: マジックビーンズ

アクティブレコードとは

データベーステーブルあるいはビューの1行が1つのクラスにラップされ、オブジェクトのインスタンスがそのデータベースの1つの行に結合される。このクラスはデータベースアクセスのカプセル化も行う。オブジェクトの生成後は、保存メソッドで新しい行がデータベースに追加される。 オブジェクトが更新されると、データベースの対応する行もまた更新される。

出典: http://ja.wikipedia.org/wiki/Active_Record

Page 5: マジックビーンズ

アクティブレコードとは# 1 件取得 bug = Bug.find(1234) # SELECT * FROM bugs !# 新規作成 bug = new Bug.new bug.summary = ‘保存時にクラッシュが発生’ bug.save # INSERT INTO bugs (summary) VALUES (‘保存時にクラッシュが発生’) !# 更新 bug = Bug.find(1234) # SELECT * FROM bugs bug.summary = ‘保存時にクラッシュが発生’ bug.save # UPDATE bugs SET summary = ‘保存時にクラッシュが発生’ WHERE id = 1234 !# 削除 bug = Bug.find(1234) # SELECT * FROM bugs bug.delete # DELETE FROM bugs WHERE id = 1234

Page 6: マジックビーンズ

弊害1: モデルがデータベーススキーマに依存する

• アクティブレコードを用いると、n 個のテーブルがある場合、n 個のモデルクラスが必要になる。

• データベースをリファクタリングする際にモデルクラスだけでなく、それを使う側のコードまで変更する必要がある。

Page 7: マジックビーンズ

弊害2: CRUD 機能を公開してしまう

• アクティブレコードを用いると find, create, update, delete などのメソッドを公開メソッドとして直接呼び出せてしまう。

• ビジネスロジックとして実装されたメソッドを利用せず、公開

CRUD メソッドを利用できてしまう結果、要件を満たさないコードが書かれる可能性がある。

• 書籍中ではバグ担当者をアサインしたらメール通知するはずだが、メール通知が迂回されてしまうという例で紹介されている。

• 仮に要件を満たせたとしても、公開 CRUD メソッドの利用はビジネスロジックがアプリケーション層まで流出する結果になる。また、複数箇所に同じロジックが重複する可能性がある。

Page 8: マジックビーンズ

弊害3: ドメインモデル貧血症をもたらす• モデルがデータアクセスオブジェクトとしてしか利用されず、ビジネスロジックの多くがアプリケーション層にあるコントローラーオブジェクトやサービスオブジェクトに記述されている状態を「ドメインモデル貧血症」という。

• 参考: http://capsctrl.que.jp/kdmsnr/wiki/bliki/?

AnemicDomainModel

• CRUD メソッドを公開してしまった結果として起こりやすい。

• ビジネスロジックを持たないモデルと巨大で手続き的なサービスオブジェクトで構成されることになり保守性が低下する。オブジェクト指向のカプセル化の考え方に完全に違反している。

Page 9: マジックビーンズ

弊害4: ユニットテストが困難• モデル、ビュー、コントローラ、すべてのレイヤーにおいてユニットテストが困難になる。

• モデル: データベースと密結合になるため、本番と同等のデータベーススキーマを準備して実際に接続する必要がある。

• ビュー: モデルが単なるデータコンテナとして使われると表示処理にも複雑なロジックが生じるため、実際のレスポンスとして返される

HTML を生成し、それに対してテストする必要がある。

• コントローラ: ビジネスロジックがアプリケーション層に漏れだしているため、ビジネスロジックのテストのために HTTP リクエストとレスポンスをエミュレートする必要があり煩雑になる。

Page 10: マジックビーンズ

“Railsの”アクティブレコードのさらなる弊害

Rails のアクティブレコードの場合、データベースに加えてフォームとも密結合になる。

<%= form_for(@bug) do |f| %> <div class="field"> <%= f.label :summary %><br> <%= f.text_field :summary %> </div> <div class="field"> <%= f.label :description %><br> <%= f.text_area :description %> </div> <div class="actions"> <%= f.submit %> </div> <% end %>

Page 11: マジックビーンズ

“Railsの”アクティブレコードのさらなる弊害• テーブルとフォームが綺麗に対応しない場合にはモデルにフォーム由来の実装が現れる。

• (例) メールアドレスの確認入力

• (例) 「利用規約に同意する」チェックボックス

• バリデーションルールやエラーメッセージの管理もモデルの責務として実装される。

• Rails でアンチパターンに陥ると、モデルにはバリデーションルールのみがあり、ビジネスロジックはコントローラに流出してしまう。

Page 12: マジックビーンズ

アンチパターンの見つけ方• 「モデルにカスタム SQL クエリを渡すにはどうすればいい?」

• モデルにカスタム SQL を渡す必要はない。クエリをカプセル化し、アプリケーション層から隠ぺいする。

• 「複雑なモデル操作をすべてのコントローラにコピーすればいいのだろうか、それとも、親の抽象コント ローラクラスに 1 回だけコーディングすればいいのだろうか」

• どちらの方法でも安定性や保守性は得られない。複雑な手続きがあるのであればモデルのメソッドとして公開する必要がある。

• 「モデルのユニットテストを行うために、データベースフィクスチャをもっと書かなければならない」

• モデルのテストではなくデータアクセスのテストをしている可能性がある。

Page 13: マジックビーンズ

アンチパターンを用いてもよい場合• アクティブレコードそのものは優れたデータアクセスパターンであり、それ自体はアンチパターンではない。

• テーブルの各行に対してシンプルな CRUD 操作ができるだけで十分なアプリケーションも多く、その場合には DAO とモデルを同一のものにすることで設計を単純化できる。

• プロトタイプ作成においては作業省力化の役に立つ。

Page 14: マジックビーンズ

http://a-suenami.hatenablog.com/entry/2014/11/12/221805

“何が必要か/本当に必要かがわかるまでは開発しないか最低限の開発で検証を重ねるのがいいと思いますし、その結果コアドメインが姿を表したらユビキタス言語を構築してモデリングをするべきでしょう。逆にノンコアドメインについてはフレームワークや既存のパターンを使って低コストに済ませてしまうのがよいと思います。”

Page 15: マジックビーンズ

解決策• モデルがアクティブレコードを「持つ」ようにする

• モデルを理解する

• ドメインモデルの使用

• プレーンなオブジェクトのテスト

• 現実的に考える

Page 16: マジックビーンズ

レイヤー化アーキテクチャ• 「エリックエヴァンスのドメイン駆動設計」で紹介されているアーキテクチャパターン。

• ある層の要素は同じ層の別の要素か、より下の層の要素にしか依存しないことを原則とする。

ユーザ・インターフェース層ユーザへの情報の表示、ユーザからの入力の解釈を責務として負う。

アプリケーション層ソフトウェアがやるべき仕事を定義し、問題を解決できるようにドメインオブジェクト間の調整をする。ドメインに対する知識を持たず、薄く保たれる層。

ドメイン層 ビジネス上の概念やビジネスルールを表す責務を負う。

インフラストラクチャ層上位のレイヤを支える技術的機能を提供することを責務とする。

Page 17: マジックビーンズ

オブジェクトの責務の割当てパターン ( GRASP )

• 情報エキスパート ( Information Expert )

• 生成者 ( Creator )

• 疎結合性(Low Coupling)

• 高凝集性(High Cohesion)

Page 18: マジックビーンズ

ドメインモデル• ドメインモデルについては「エリック・エヴァンスのドメイン駆動設計」で詳しく述べられている。

• ドメインモデルとは対象ドメインに関心事をアプリケーションで表現したものであり、MVC アーキテクチャにおける本来の「モデル」もそうであるべきである。

• エリック・エヴァンスの書籍では永続化層とドメイン層の仲介役としてリポジトリパターンが紹介されている。

Page 19: マジックビーンズ

プレーンオブジェクト• データベースの構造に依存しないプレーンなオブジェクトはテストしやすく、またテストの実行速度も速い。

• 特に実装言語が Java の場合、それが特定のフレームワークやアーキテクチャに依存してないことを強調するために POJO ( Plain Old Java

Object ) と呼ばれる。

• 他の言語でも同様に POXO ( X は言語の頭文字 ) と呼ぶケースはある。

• ドメインモデルを正しく設計してビジネスロジックをそこに隠蔽すると、コントローラやビューのテストではそれらのモデルをモックやスタブに差し替えることができ、数多くの分岐をテストしなくてよくなる。

Page 20: マジックビーンズ

Before & After

出典: 『SQLアンチパターン』P269, P276

Page 21: マジックビーンズ

ちなみに…

若干古い書籍ですが、「エンタープライズRails」という書籍に models/physical と

models/logical を作成して、同様のことを実現する方法が紹介されているのでご興味ある人はぜひ。

※まわし者ではありません。

※時間なくて詳細を資料に盛り込めませんでした、すいません。

Page 22: マジックビーンズ

最後にちょっと小話。 Rails を使ってる場合の現実的な

落とし所について。

Page 23: マジックビーンズ

公開メソッドについて• モジュールの include で見た目の複雑性を下げる。

• コードレビューによるチェックで基底クラスの公開メソッドを使わないようにする。

Page 24: マジックビーンズ

フォーム / サービスオブジェクトの利用

http://a-suenami.hatenablog.com/entry/2013/12/06/092146

Page 25: マジックビーンズ

まとめ データベースとモデルは区別しましょう。

!

DOA の人とモデルの人は殴りあってないで もっと仲良くしましょう。(マジで)