Hatena::Groupcatalyst

dann@catalyst このページをアンテナに追加 RSSフィード

2008-03-05

DBを直接モデルとして扱うのは悪か?

00:48 |  DBを直接モデルとして扱うのは悪か? - dann@catalyst を含むブックマーク はてなブックマーク -  DBを直接モデルとして扱うのは悪か? - dann@catalyst  DBを直接モデルとして扱うのは悪か? - dann@catalyst のブックマークコメント

DBICをControllerから直接呼ぶのは本当に悪なのか?、これは一概には言えないんじゃないかと思っているのだけれど、Perl界隈でもそうなんだろうか。以下のようにレイヤ分割して、レイヤ間を疎に保つという話は、Javaの界隈では割によくやられていた話だ。大体以下のような構成だ。

Action -> Service-> Domain Model -> DAO -> OR Mapper

レイヤ間をDIコンテナで繋いで、各レイヤ間の依存関係は最小にして、テスタビリティを高めるというのが定石だった。ドメインモデルがDBに直接依存しないようになっていて、DAOの切り替えができるようになっているわけだ。さらに、Service、Domain Modelは、WebのフレームワークやORマッパなどに依存していない。

ただ、こういう構成は、各レイヤを繋ぐためのレイヤを作るコスト、レイヤを繋ぐデータのオブジェクトを作るコストがある。DBのレイヤが変わると、DTOが各レイヤに波及して、上位レイヤでも作り直しが発生する。Domain ModelのロジックはDBによらずにテストできるようになったが、疎に結合するようにするために数多くのレイヤを用意しなければいけなくなってしまった。

Domain Driven Designが2、3年前に流行った当時は、確かにこういう構成もありなんじゃないかと思っていた。けれど、実際にやってみると、ERモデルとオブジェクトモデルがあわないこともあるし、上記で書いたようにレイヤを用意するコストとレイヤ間を繋ぐコストが高くなってしまうこともある。

上記のような疎にするために余計なレイヤを挟むアーキテクチャよりも、ERモデルを直にモデルにするのでも良いのではないかと思うことが最近は多い。上記のような構成は、レイヤ間の疎結合を実現するために、無駄なコストを払いすぎているんじゃないか思うのだ。

Railsがでてきたときに衝撃的だったことのひとつは、Domain Model, DAO, DBIC部分はひとつにまとめて、ERモデルをそのままモデルとして扱ってしまうというアプローチだった。モデルが簡単に生成できて、DBを直にあらわしたモデルが簡単にテストできる仕組みさがあれば、密にモデルがDBに結合していても機能するんだということに驚いた。

それで結構十分だと思えるケースが結構多いことがあるんじゃないかと思う。だから、それだけRailsが受け入れられたんじゃないかと思う。

ここで言いたいのは、DBのモデルであるERモデルを、そのままモデルとして扱うのは絶対悪とはいいきれないんじゃなかってことだ。ORマッパを置き換えることなんて開発中にはまずないし、ORマッパ依存があったとしても、それは大きな問題じゃない。

モデルはDBに完全に依存していて、かつ各レイヤ間は密に結合していても、そこには無駄なものというのがひとつもなければそのほうがいいんじゃないかって思うのだ。

密結合の問題は、モデルがDBに依存してしまい、モデルのロジックのテストがDBなしでないとやりにくくなってしまうことだけだ。これはテスタビリティが下がり、プロダクト規模が大きくなればうまくいかなくなることがあるんじゃないかと思うこともある。

ただ、ここにはRubyPerlのようなDynamic Languageには解があると思う。テスト実行時にはクラス定義を上書きできるから。これがDIコンテナがRubyPerlでそこまで必要とされないひとつの理由なんだと思う。自分が考える、Perlのフレームワークの理想は、Railsが描くものにきわめて近い。疎にするための無駄なレイヤは作らないというのを基本スタンスをとるということ。

レイヤを疎に保つようににすれば、間に余計なレイヤが存在するようになるし、開発環境のサポートのない環境では、余分なレイヤはテストとコードの二重の変更が重荷になってくる。だから、Javaですら大変だったことは、同じようにPerlRubyでやればさらに大変になることは想像がつく。

だからこそ、簡単にDBICのモデルを直にモデルとして扱うことが悪になるとはいいきれないんじゃないかと思うのだ。

という話をCatalystConで話し合ってみたい!

Railsを触るようになってからドラスティックに考え方が変わったんだけれど、逆にこの密結合モデルは、テスタビリティ上、解決できない問題があるのかっていうのをもっと考えたい。疎に保つアプローチは、時にオーバースペックになりがちってことが、SpringのようなDIコンテナを使っていたときに思ったことの一つで、ピタリとはまるケースもあるんだけれど、そうじゃないケースも結構あるってこと。

自分が使うときのレイヤ分け

00:48 |  自分が使うときのレイヤ分け - dann@catalyst を含むブックマーク はてなブックマーク -  自分が使うときのレイヤ分け - dann@catalyst  自分が使うときのレイヤ分け - dann@catalyst のブックマークコメント

以下くらいの形で成り立つんじゃないかと最近思ってる。Javaerなときは、DIコンテナで繋ぐ形しかありえないと思っていたのだけれど、Railsを触ってからは大分変わった。

DBICのモデルに固有のロジックは、DBIC部分に。DBICのモデルをまたがるロジックは、Service部分にという形に今はしている。DBICのモデルは、Controllerに見せる形になっている。

Controller -> Service -> DBIC

Controller -> DBIC

Catalystを使ったJava風なアーキテクチャ

00:48 |  Catalystを使ったJava風なアーキテクチャ - dann@catalyst を含むブックマーク はてなブックマーク -  Catalystを使ったJava風なアーキテクチャ - dann@catalyst  Catalystを使ったJava風なアーキテクチャ - dann@catalyst のブックマークコメント

Controller -> Service -> Domain Model -> DBIC

こういう形にすることのメリットは、

  • ServiceやDomain ModelがCatalystのフレームワークの依存から解き放たれること。
  • ControllerのロジックはServiceやDomain Modelに移り、Controllerの見にくいテストが減ること
  • ServiceやDomain ModelはPOJOならぬPOPOになるため、テスタビリティが高まること

デメリットは、

  • 疎に結合するために余分なレイヤを挟むことによるコストの増加。これはコード、テスト、DBレイヤ変更時の上位レイヤへの変更コストを含む。

こういう構成でやるときには、モデルのロジックがとても複雑な場合、モデルが多数のモデルに依存している場合なんじゃないかなって思う。

この構成でやるほど複雑な場合には、DIコンテナを使いたいなぁと。依存関係の解決はセッターインジェクションで解決されていて、ビジネスモデル側は具体的に何がインジェクションされるのかを知らない形にしたいから。具体的には、以下のような形にしたい。storageへの依存は、createのパラメータには渡さず、何に依存しているかはDIコンテナ側で事前に解決させたいなって思う。

MyApp::Model::BlogEntry->create({ 
    blog => $blog
    title => $entry_title
    content => $entry_content
});

Catalystを使っていて困ること

00:48 |  Catalystを使っていて困ること - dann@catalyst を含むブックマーク はてなブックマーク -  Catalystを使っていて困ること - dann@catalyst  Catalystを使っていて困ること - dann@catalyst のブックマークコメント

  • DBICのモデルを直に扱うことによるテスタビリティの低下
    • ServiceやDBICのモデルのロジックがDBに依存してしまう
  • Serviceレイヤを設けないと、namespaceの問題で再利用性がゼロ
  • Controllerのテストをしようとすると、mockのリクエストを作ってテストが見難くなること

一つ目の問題は、Railsライクにテスティングフレームワーク側でDBをテストしやすい仕組みを用意するか、DBIC側のMockを簡単に作れる仕組みを用意するか、DBICのモデルをテスト実行時に上書きするかの方法で解決することになるんじゃないかと思う。ここは何がいいのかは迷っているけれど、なにかいい解はある気がするんだなぁ。Rails界隈でどうやってテストやってるのか、今度聞きにいってみようかなぁ。

2番目の問題は、Catalyst::Model::Adaptorで、ある程度解決。

3番目の問題は、極力Controllerにロジックを書かないようにすることで、ほぼ解決。

1番目の問題を綺麗に解決できる方法があれば、殆ど問題はないんだけどなぁ。

ゲスト