会社で、Ruby on RailsのバックグラウンドがあるけどJavaはそれほど深くない人向けに、Javaでのオブジェクト指向設計とか、パッケージやクラスの分け方、置き方を説明した方が良いかなーという場面が出てきました。
が、いざ棚卸ししてみますと、特にドメインモデル周辺について自分も良く理解してない点が多々有りまして。なんかざっと記事を漁ってみまして、それのログというかメモ書きになります。
全体的に「如是我聞」な内容・・・どころか、そのまんま載せるのに力不足でもしかしたら曲解・誤解して書いてる内容があるかもしれません。とにかく参考資料(記事のURLや、PoEAA本のP数)については載せましたので、不審に感じたら元資料をあたってみてください。
うろ覚えになってたのが、「なんかservice layerとかdomain modelとかbusiness logicとかに分けるんだよなー」レベルでしたので、そもそも service layer ってなんだろうと適当にググりました。
・・・SOAは飛びすぎた・・・。
TERASOLUNA というフレームワーク用の解説になってますが、うろ覚えレベルの知識でも、「ああ、そうそう、こんなレイヤー分けだったわ。」的な図が載ってて分かりやすい。
「ドメイン」というのが分かりづらいのですが、そのアプリの解決すべき問題を表現するものらしいです。
ここで分かりづらいのが、「ドメインロジック(domain logic)」とか「ドメインモデル(domain model)」みたいな用語が乱れ飛んでる点でした。
この記載を参考にすれば、システムに関わる実体とその関係を説明する、概念レベルの模型化、ということで、そんなに違和感は感じません。
ところが、Martin Fowlerの"Patterns of Enterprise Application Architecture"(PoEAA)にも「パターン」の一つとして「Domain Model」というのが出てきます。
こちらはUMLで図を書いて模型化する・・・という話ではなくて、概念に名前をつけた(ドメイン)オブジェクトに振る舞いも持たせるパターンがPoEAAの書くところのDomain Model パターン・・・らしい、です・・・。(これについて断言できるの、PoEAAの著者だけちゃうんか?めんどくさいから全部に「如是我聞」付けたろか。)
例えばblogシステムだったら、「記事」という概念がドメインとしてそのまま、Articleというオブジェクトで表現されます。記事の属性やテキストデータが、オブジェクトのプロパティとかフィールドに格納される。で、記事の作成とか編集が、Articleオブジェクトのメソッドとして公開されます。多分こんな感じ。
ところが、機能が増えてくると色々、本来の「記事」とは関連しないような操作が要求されるようになります。
例えばユーザがある記事を "watch" して、記事が更新されたりコメントが投稿されたらメールで教えて欲しいとか。
「コメント」は「記事」に関連する別概念として切り出したほうが良いでしょうか?そうなると、データの表現として、「記事」オブジェクトと「コメント」オブジェクトはどう関連付ければ良いのでしょうか?
ここで、どうやら2つの方向性があるっぽいです。(PoEAA, p117)
1つ目は、「シンプルな」Domain Modelと呼ばれてるもので、RDBなどでテーブルをそのまま1つのドメインとして切り出した形式で、Active Record パターンなどが使われるようです。
2つ目は、「リッチな」Domain Modelと呼ばれてるもので、もっと複雑なロジックやデザインパターンを活用して、1つのドメインに対応するデータベースの実体を特定できないほど複雑なタイプで、これは Data Mapper パターンが上手く当てはまるようです。
アプリやシステムを構築していくと、Domain Modelに収まらないような概念や処理が出てきます。例えば決裁管理のワークフローを実現するようなアプリケーションだと、決裁処理が進むと、対象となる書類、申請者、決裁者、など複数の概念(ドメイン)が関連して状態が変化します。
そうなった時に導入を検討するのが、アプリケーションとして必要なドメイン処理をまとめたレイヤーで、これが PoEAA p133で "Service Layer"パターンとして紹介されてます。
多分、本来の Service Layer は複数のDomain ModelかTransaction Script (PoEAA p110)を連携させるだけの薄いラッパー処理になることを意図していたように思われます。
こんな感じで、ひとつの境界線として機能するわけです:
UI/Batch/HTTP <-> Service Layer <-> Domain Models / Transaction Scripts
ところがだんだん、Domain Model の扱いがおざなりになってきて、
UI/Batch/HTTP <-> Service Layer <-> SQLなど永続化処理、各種ビジネスロジック <-> 値オブジェクト / エンティティ
のようになってきて、"Domain Model"がデータ表現だけを扱う状態になってしまい、Service Layerの中に処理がどんどん入ってきてしまう、そんな設計が増えてきてしまったようです。
「値オブジェクト」というのは、識別する必要のないラベル値、量などを表現するパターンです。
「エンティティ」というのは、ドメインの表現により強く関連し、ID値を持っていて、他のデータと区別する必要があるオブジェクトです。ユーザや記事、グループなど、一般的なCRUD操作が必要となるデータ表現全般に当てはまるオブジェクトです。
この辺の記事を読んでみると、例えば「ユーザ」の1人を表現するのが「エンティティ」で、その性別を表現するラベル付の値は「値オブジェクト」として考えて差し支えなさそうです。
"Service Layer"の誤用の話題に戻すと、本来のDomain Modelとの関係から離れてしまって、単なるValue ObjectやEntityオブジェクトを"Domain Model"と呼んでしまい、ドメインに関連するロジックがすべて Service Layer で実装されてしまう設計が問題視されたようです。
前述のような、Service Layerに処理が書かれすぎてしまい、肝心の Domain Model が Domain Model の本来の機能を果たしていない、という状況について Martin Fowler たちが「ドメインモデル貧血症」というアンチパターンを命名しました。
Javaとは離れてしまいますが、MSDNマガジンで「ドメインモデル貧血症」、「リッチなDomain Model」、「値オブジェクト」について分かりやすい記事が公開されています。
まだ調査が進んでいないのですが、そもそもPoEAAでもDomain Model自体が「ものにする」のが難しいパターンで、コンサルタントを呼んで学んだり、トレーニングを積んだり、コーチングを受けて使えるようになるまでは Transaction Script やData Mapperを採用する手もある、と書かれているほどです。(PoEAA, p119, "When to Use It")
あくまでも私見ですが、それほどまでに習熟が難しい"Domain Model"パターンを、限られたリソースしか投入できない現実の開発現場で、本当にMartin Fowlerたちが目論んだどおりの意図に厳密に従い適用するのは、そもそも現実的な話なのでしょうか?
Data Modelのメリットとしては、OOPの作り方を活かせるため、処理が分散しない、という点が挙げられます。しかし、本当にそのメリットを享受するだけの価値が、Data Modelの学習コストでペイ出来るんでしょうか・・・???
Data Model「でなければならない」というのは、法律で定められてる訳ではないので、ある程度のリスクを取った上で、Transaction Script や Data Mapper を適用した方が適切なケースはある筈です。
Service Layerの切り分けについても、粒度の問題があります。アプリのユースケースに対して、Service Layer や Domain Model, Transaction Scirpt の粒度をどのように調整するのか、という観点が出てきます。
これについては以下の記事が参考になると思います。
上手くDomain Modelを組み立てられないような処理が出てきたので、徐々にService Layerが太っていった可能性もあり、そうした場合の解としてはやはり、Service に組み込む、というアプローチがあるようです。
実際にアプリケーションを設計する際は、プレゼンテーション層のことも忘れてはいけません。
ここで、Domain Model辺りを検討していると、「はたして、プレゼンテーション層がダイレクトにDomain Modelに触るのは適切だろうか?」という疑問が出てきます。
ましてや Service Layer を導入すると、そもそも Service Layer は境界線として機能するため、プレゼンテーション層が Service Layer を介して Domain Model のオブジェクトを取得できてアクセスできてしまうのはどうも一貫性に欠けている気もします。
そういう場合に、DTOに変換することで、データ転送のためだけの、シリアライズ可能なオブジェクトとして扱うパターンが「データ転送オブジェクト」(DTO)のようです。(PoEAA, p401)
シリアライズが出てくるのは、ネットワークを介した分散システムを想定しているからのようです。
MSDNマガジンの記事で、調度良いのがありました:
Railsの場合、ActiveRecordが"model"の役目も負っていることにより、なんでもかんでもmodelに入れたり、かなり設計技法や概念的な部分で開発者が困惑し、誤用したり乱用するケースがあるようです。
さらにこれが、MVCパターンの"Model"の部分と絡みあってしまい、混乱に拍車をかけてる感じが・・・。
このへんの議論については、ひがやすをさんの意見が一番バランスが良い気がします。
そもそも、どこに何を置こうと、制約や当初の前提が崩れることはどうしても出てきてしまうので、そこはソフトウェアテストの自動化を適用してService Layerに対するテストを自動化した上で、リファクタリングで対処する。これが現実的なアプローチに思います。
ぶっちゃけEJBを使う予定は当分ないので、ググって見つかった上位記事だけを数点メモ。
Martin Fowler氏による"Service Layer"パターンのPoEAA記事(PoEAA本とほぼそのままのよう)
DDDについての日本語解説記事
MSDNマガジンより。.NETテクノロジーをベースとした話になっていますが、クラス分けやレイヤー分けの考え方、アドバイスについてはJavaでも十分展開可能な内容となっており、参考になります。
SpringでのService Layer, Domain Model の実装例解説
PHPでのService Layer, Domain Model の実装例解説
その他の議論、解説:
仏教史とかキリスト教史かじってるせいか、経典や師の言葉の解釈がどんどん枝分かれして、極端に走ったり揺れ戻ったり、互いに批判したり、誤用されたり濫用されたりとか、そのまんまですよ奥様・・・。
「パターン」は技法というか考え方、思想、表現方法であって、「こうでないと間違い」という類のものではないはずです。というか、「パターン」の本来の意図どおりに使われて無くても、結果としてそのアプリケーションなりシステムなりを、妥当な品質と妥当なコストと妥当なメンテナンス性と妥当なソースコード可読性でもって構築できていれば、現実的には問題は無いはずなんです。
・・・と考えるのは異端なんでしょうか。
(まぁ、まだ勉強途中の開発者がそのアーキテクチャをみて、「ふ~ん、これが○○というパターンの適用例なのか」と刷り込み学習してしまう危険性はありますが。)
あくまでも、超個人的な意見としてですが、一番気をつけなければイケないのはデータ表現方式のレイヤー間でのミスマッチだと思うんですよ。
次点でソフトウェアによるテスト自動化。
Webアプリにせよ、ネイティブアプリにせよ、あるいは他システム間連携のNW分散システムにせよ、ファイル読み書きしかしないバッチ処理にせよ、絶対に、「入力/出力」のデータフォーマットと、内部のビジネスロジックで扱い易いデータフォーマットにミスマッチがあるはずなんです。
もちろんシステムの規模や言語によってそれは変動して、単純なCSVレコードを処理するような場合だとほとんど入力・出力と内部処理の表現のミスマッチがない場合があるでしょうし、逆に、WebアプリだとHTTPでやってくるパラメータ形式と、OOPの言語やライブラリで扱い易いオブジェクト表現にはそうとうなミスマッチが出てくると思います。
もう一つミスマッチが発生しやすいのが、ビジネスロジックと永続化レイヤーの間です。
これも、SQLだったりシリアライズ処理だったりと、それぞれの言語や永続化機構、ライブラリ、フレームワークによりミスマッチの程度は異なってきます。
で、このミスマッチを、無理やり、メタオブジェクトプログラミングとか、リフレクションとかを使って「単純なケースなら自動マッピングOK」としてしまうと、色々崩壊してくると思うんですよ・・・ってこれも私見ですが。
自動マッピングならコード書かなくて済むじゃない・・・と言われると思うんですが、案外、テストさえ自動化できてれば、多少手作業でも素直に、愚直に、自前で変換処理を作ったほうが、後々自動マッピングの罠や制限で苦労しなくて済む場合が多そうに思うんですが・・・どうなんでしょうかね。もちろんそのへんの地雷とか罠とか、自動マッピング処理が適用できる限界点を把握した上で付き合う分には問題ナッシングと思います。
超個人的な意見ですが、あんまりOOPの継承とか派生をこのへんの仕組みに適用しすぎても、却って後々の変更に弱くなりそうな気がします。単純なEntityとテーブルマッピングにしておいて、多少Service Layerがfatになっても良いのでロジックはあまりEntity側に入れない、代わりにService Layerに対してAPIとしてのテスト自動化を進めて、後々のリファクタリングやリグレッションテストを効率化する、という方が良い気がするのです。
OOPとしての筋の良さよりは、品質、メンテナンス性、可読性、そして理解しやすさと、「誤解のしにくさ」が何よりも大事かと思います。
そうした点で、徒に Domain Model とかレイヤー分けとかモデリングの「絶対的な正しさ」にこだわって凝ったアーキテクチャ設計にしてしまうと、後から入ってきたり突発的にお手伝いに入った開発者がすぐに活躍できない状況になってしまいそうです。
OOPが活用されないと可読性や再利用性が・・・と言われそうですが、これをテストの自動化とリファクタリングでカバーするイメージです。
でないと、Domainがドーノとか、あるべき設計は~というのを議論したり勉強するのだけで何十時間も、何年も費やすことになってしまい、「ぼちぼち妥当で可読性と理解しやすさ、誤解のしにくさ」を備えたコードを作る機会を逸してしまいそうです・・・。
とりあえず今回の調査はここまで。
※前にこんなの書いてた : 日記/2012/12/31/SQLをうまく扱うプログラム設計メモ