DB Magazineの良記事

何ヶ月か前から、Struts + DI + O/Rマッパーのサンプルアプリケーションを集めている。
ソースがあるだけではダメで、なんでそういう設計になっているのか、作者の説明付きのものを探している。
で、DB Magazine 7月号の「DIコンテナをベースにしたアプリケーション実装モデルの作法」が、ぴったりの記事だった。
サンプルアプリは、特定顧客の売上情報(ヘッダ&明細)を一覧する、というもの。


読みたかったポイントは2点。

  1. このサンプルアプリは、データアクセス層が返すentityクラス(データベーステーブルと1対1に対応するJavaクラス)と、サービス層が返すdtoクラスを別々に作成しているが、それはなぜ、と説明しているか。また、entityクラス <-> dtoクラスのデータの詰め替えは、誰がどのように行うのか。
  2. 現代的なO/Rマッパーは、(業務アプリでは必然的に発生する)集計行をどのようにさばくのか。1ショットのSQLで明細行と集計行をunionしたりwith rollupしたりした場合、集計行もきちんとJavaクラスに収容してくれるのか。


2.は集計行がないアプリということで残念ながら不明。
1.については詳細な記述があった。

(dtoパッケージは)entityパッケージ内にあるクラスと名前以外は同じである。なぜ、似通った構成のクラスを別パッケージとして管理しているのだろうか。
簡単に言えば、entityパッケージ内のドメインオブジェクトをクライアント層にまで持っていきたくないからだ。
もし、entityパッケージ内のクラスをクライアント層にまで持っていくと、entityパッケージ内のクラスの変更の影響を、今回扱う実装モデルのすべての層が受けることになる。
<中略>
そのために、entityパッケージ内のクラスをすべての層で使うと、テーブルレイアウト変更の影響を受けやすいアプリケーションになってしまう。

これはどうだろう。
entityクラスとdtoクラスが別物であるべき、というのは100%賛成。
理由は以下の通り:

  • entityクラスはドメイン写像で、dtoクラスは特定のユースケースをサポートするためのものだから、当然形が違う。例えばdtoクラスには10個のテーブルをjoinした結果表を収容したいかもしれない。
  • dtoはプレゼンテーション層で使用するものなので、同じデータをドメインオブジェクトとは異なる形式で返せなくてはならない。例えばCustomerクラスの「顧客の身長を返すメソッド」で、身長が未入力だった場合、「ドメインオブジェクトなら数値の0を返したいが、dtoなら空文字列を返したい」という場面があったりする。
  • dtoはプレゼンテーション層で使用するものなので、ユーザが妙なデータを投入してきても、例外を投げたくない(例外を投げるのはvalidationのタイミングまで待ちたい)。entityは永続化されるので、おかしなデータをsetされたら即例外を投げたい。

しかし、「両者(entityとdto)を分けると変更に強くなる」というのは意味が分かるようでわからない。


例えばcustomerテーブルに「携帯メールアドレス」フィールドを新規追加した場合。
entityとdtoを分離する設計ならば、「携帯メールアドレス」に関心のない機能が使用するdtoを修正する必要がない。それが利点だ、という主張だと思う。
この場合、変更が必要なクラスは、

  • entityクラス
  • 「携帯メールアドレス」を参照する機能が使用するdtoクラス
  • entityとdtoとのデータの詰め替えを行うクラス

の3種になる。
entityとdtoを分離しない設計=Dao層が返すentityクラスがViewまで直通する設計であれば、修正するのはentityクラスのみで済む。
つまり、テーブルレイアウトのちょっとした変更であれば、entityとdtoの分離は、かえって影響範囲が広くなる。


また、entityクラス <-> dtoクラスのデータの詰め替えは誰がやるのかの答えは以下の通り:

サービス層はdtoパッケージ内のクラスとentityパッケージ内のクラスの相互の値の受け渡し(値の詰め替え)の責務を持っている。
この値の受け渡しの処理を書くのはなかなか面倒なのだが、手間をかけるだけの価値があるのは確実だ。

つまり、サービス層でハンドコーディングして値の詰め替えを行うということ。
これがサービス層の「責務」ということになっているが、これっていわゆる「ビジネスロジック」なのだろうか。違うよね*1

何だか妙な感じがするのはなぜか

この手の設計はいろんなところで見るのだが、なかなか腑に落ちない。
妙だなあと思うのは:

  • サービス層で特定ユースケースをサポートするDTOを構築している。そのため、テーブルレイアウトやプレゼンテーションの変更で、必ずサービス層の修正が発生する。要するに上から下まで全部修正が必要になる。
  • サービス層がプレゼンテーションの準備作業をしている。「ビジネスロジック」だけに集中していない。
  • Dao層がデータ構造を隠蔽しない。データ構造の変更の影響は、変更されたentityにアクセスするサービス層が *個別に* 吸収しなくてはならない。
  • entityからdtoへのデータ詰め替えは、SQLの結合・射影と同じことをしているように見える。つまり、SQLでできることをわざわざハンドコーディングしているように見える。


端折って結論を書くと、entityのレイヤがなくなるといいのかなあと思う。

  • ドメインモデリングはしない。データモデリングだけする。
  • entityパッケージは廃止。Javaクラスでデータ構造の写像を作るのをやめる。
  • Dao層から直接dtoを返すようにする。これによりサービス層でのデータの詰め替えが不要になる。サービス層はビジネスロジックのみに集中する。
  • dtoはMapをimplementする(=マップ厨)。

こんな感じにすると、customerテーブルに「携帯メールアドレス」フィールドを追加した場合、修正するのはプレゼンテーション層だけ(例えばJSPだけ)にできる。
ドメインモデルの放棄により、多分いろいろな問題が噴出するのだと思うが、そこは大野耐一の精神で。問題があるからといってやめないで、そこからもうひとつ変えてみるということで。

*1:実は、ビジネスロジックの定義て何なのか知らない。特定の画面のためにオブジェクト間のデータの詰め替えを行う、という操作をビジネスロジックと見なす流派もあるのかもしれない。