Hibernate in Action における DTO についての考察

もう10年以上前の本、Hibernate in Action に書かれている DTO についての考察です。

HIBERNATE イン アクション

HIBERNATE イン アクション


何というか、文章が読みにくい部分ががあるため、コメントを挟みながら紹介します。


サーブレットベースのアプリケーションと、ビジネスロジックとデータアクセスがEJBコンテナにおいて実行されるアプリケーションとの間の最も重要な違いは、層を物理的に分離できるかどうかである。

J2EEの当初の目標は、分散コンポーネントの基盤を標準化することでした。

JTA による分散トランザクションや JNDI によるネーミングディレクトリサービスRMI/IIOP や JMS による通信基盤などを使い、EJB を異なるコンピュータに分散して配置し、ネットワーク越しに通信することでアプリケーションを構築するものです。

サーブレットなどで作ったプレゼンテーション層から、別 Tier (物理階層)に配備した EJBビジネスロジック層を構成するというのが一般的な構成でした(論理的なレイヤ分割だけで、物理階層としては分割されていないものも多かったわけですが)。

もちろんこのようなアーキテクチャは殆どのアプリケーションには重厚過ぎますし開発も困難でした(ホームインターフェースとリモートインターフェース、デプロイメントディスクリプタ・・・)。 さらにネットワーク経由した際の処理のオーバーヘッドでパフォーマンスが出ないなどの問題も多くありました。


もしEJBコンテナがサーブレットエンジンとは異なるプロセスで実行されるなら、サーブレット層からEJB層へのリクエストを最小化することが絶対に必要だ。 プロセス間リクエストを行うと、そのたびに遅延が増し、その結果、より多くの、あるいはより長いデータベーストランザクションが必要になり、それがアプリケーションの応答時間の増加や並列性の低下を引き起こすことになる。

理想を追った J2EE でしたが、すぐにパフォーマンスという現実的な問題に当たりました。

エンティティビーンを素直に使った場合、クライアント側からgetUserName() getAge() getSex() といった細粒度のリモート呼び出しが頻発し、パフォーマンス低下が深刻な問題となりました(ローカルのEJB呼び出しは RMI を使わないなどのアプリケーションサーバ側での工夫もありましたが)。


それゆえ、単一のユーザリクエストに関する全てのデータアクセスは、単一のEJB層へのリクエストの中でおこるようにすることが重要になる。 このため、ビューが必要に応じてドメインモデルオブジェクトからデータを取得する、前述の遅延アプローチは使えない。 その代わりに、ビジネス(EJB)層は、その後のビューをレンダリングするのに必要となるすべてのデータのフェッチの責務を負わなければならない。

ビューが必要な情報を都度 EJB側に問い合わせた場合のパフォーマンス低下を回避するため、EJBからは粗粒度の つまりビューが必要とする情報をデータベースから(意味を成す情報の単位で)全て取得した上でクライアントに返すようにし、クライアントからEJBへの問い合わせの絶対数を減らすアプローチが必要でした。

ビューが必要とするデータをビジネスロジック側が知っていなければいけないというのはおかしな話なので、あるビジネス上の意味をもつ情報の束(結果的にドメインオブジェクト)を返すことになるはずです。


エンティティビーンを使う既存のシステムで、すでにこの考え方を見ることができる。 セッションファサードパターンにより、システムが特定のユーザリクエストに関連するすべてのアクティビティをEJB層に対する単一のリクエストとしてグルーピングすることを可能にする。

エンティティビーンを使う場合に前述の問題があったため、ステートレスセッションビーン(SSB)をファサードとしてクライアントに公開し、クライアントはこのファサードのメソッドをサービスとして利用するという形が多くとられました(ファサードのメソッドは、ある種のユースケースになっていました)。

ちなみに ここでいうエンティティビーンとは javax.ejb.EntityBean を implements したビーンです。 データベースのエンティティとマッピングされ(テーブルの1レコードを表現するオブジェクトになります)、エンティティビーンに対して操作すれば勝手に永続化してくれるものです。

@Entity で定義する JPA のエンティティとは違い、POJOではなく EJB オブジェクトなので扱いづらく、EJB3.2 からは廃止になっています。


ユビキタスなデータトランスファオブジェクトパターンは、ビューが必要とするデータを一緒にパッケージする方法を提供する。 DTOは特定のエンティティの状態を保持するクラスであり、どんなビジネスメソッドも含まないJavaビーンまたはPOJOと考えることができる。 エンティティビーンシリアライズできず、層を超えて転送できないので、DTOはエンティティビーンの環境で必要とされる。 このケースでは、容易にPOJOシリアライズ可能にすることができるため、DTOの必要性には自然に気づくことができるであろう。

セッションファサードから何を返すかというと、エンティティビーンの情報を移し替えたビジネスロジックを含まない POJO(DTO) が必要だったということになります。



データトランスファオブジェクト再考

EJBベースのアプリケーションで、ウェブ層がドメインモデルと直接通信するべきではないとういう考えは、J2EEに深く根付いている。 著者はこの考えが一夜のうちに消え去ってしまうようなことはないと考えるし、この考えを支持することには合理的な理由もある。 しかしながら、これらの理由と、データトランスファオブジェクト(DTO)が広く一般に受け入れられた理由とを混同してはいけない。

J2EEのコミュニティが、エンティティビーンへの細粒度のリモートアクセスは遅く、そしてスケーラブルでないことに気がついたとき、DTOパターンは誕生した。 さらに、エンティティビーン自身はシリアライズ可能ではないため、ビジネスオブジェクトの状態をパッケージし、層を超えるためには別のタイプのオブジェクトが必要とされた。

「ウェブ層がドメインモデルと直接通信するべきではないとういう考え」と「データトランスファオブジェクト(DTO)が広く一般に受け入れられた理由」は出目が別なので混ぜて考るべきではない と。

DTO はパフォーマンスという現実的な問題から必然とされて一般に広まったものであり、ウェブ層がドメインモデルと直接通信することを避けるために用いられたわけではない。


現在、DTOを使うことには2つの正当な理由がある。 第一に、DTOは層の間にデータの外在化を実行する。 第二に、DTOビジネスロジック層からウェブ層を分離する。 ここでは、2番目の理由だけが当てはまる。そして、そのコストと天秤にかけられるとき、この分離のメリットは疑わしくなる。 著者は絶対にDTOを使ってはならないと言うつもりはない(他の場所では、それほど控えめではないこともある)。 その代わりに、HibernateアプリケーションにおけるDTOパターンの利用に対する支持と不支持の論拠を列挙するので、これらの論拠を読者自身のアプリケーションのコンテキストにおいて慎重に検討してほしい。

DTOを使うと、ティア間でやり取りするデータ自体を外部定義(異種間での接続を行う場合は必須)として抜き出せ、Webティアとビジネスロジックティアの分離が強制される。 しかしこの分離は果たしてコストに見合うメリットがあるのであろうか?


DTOドメインモデルに関してビューの直接の依存性を取り去ることは事実である。 プロジェクトにおいてJava開発者とウェブページデザイナーが役割を分担している場合には、これはある程度価値があるかもしれない。 特にDTOはビューにとっておそらく、より都合の良いフォーマットにデータを変換し、ドメインモデルとの関連を平坦にさせる。 しかし、著者の経験では、DTOの使用の有無にかかわらず、アプリケーションの全てのレイヤがドメインモデルと強く結合することは一般的である。 筆者はそれが悪いことだと思わないし、そのような事実を受け入れることが可能かもしれないことを提案する。

数十年前は多層レイヤがもてはやされ、全ての層をまたぐ箇所では DTO を使い、いたるところで DTO の変換処理を書くといった行き過ぎたアーキテクチャを良く見た気がします。

本当に必要だったのでしょうか? Rails はそんなことは気にせずに上手くやっています。

「アプリケーションの全てのレイヤがドメインモデルと強く結合することは」DDDのコンテキストにおいても一般的です。


DTOについての問題点を示す最初の手がかりは、データトランスファオブジェクトという名前と正反対に、まったくオブジェクトではないということである。 DTOは振る舞いなしに状態だけを定義する。 これはオブジェクト指向開発という環境では疑わしいものだ。 もっと悪いことに、DTOによって定義される状態はドメインモデルのビジネスオブジェクトで定義される状態と全く同じであることが多い。 よって、DTOパターンによって達成されているとされる分離を単なる重複だとみなすこともできるだろう。

重複は複雑さを加速し、わかりにくさを増加させる。


DTOパターンはFowler[1999]で書かれた、2つのコードの匂いを示す。 ショットガン変更(shotgun change)の匂いは、システム要求に対する小さな変更によって複数のクラスに変更が必要になるものである。 さらに並列クラス階層の匂いは、2つの異なるクラス階層が一対一対応する類似のクラスを含むのもである。並列クラス階層はこのケースでは明白である。 DTOパターンを使うシステムがItemとItemDTO、UserとUserDTOなどを持っていると言った具合だ。 ここでItemに新しいプロパティを加えるとき、ショットガン変更の匂いは自明である。 ここでは、だたビューとItemクラスだけではなく、同様にItemDTOとItemDTOのインスタンスを組み立てるコードをItem(最後のこのコードの一部は特に冗長で脆弱になる)のプロパティから変えなくてはならない。

Code smell は正確には以下ですかね。

  • Shotgun Surgery
  • Parallel Inheritance Hierarchies

DTOを使った場合のコードの重複を削減する目的で、Entity Inherits Transfer Object なんていうのもありました。 DTOとエンティティが1対1の関係の時、エンティティビーンDTO を継承させることでコード重複をなくす。エンティティビーンでは親の DTO を生成して返す。といったヤツです。


もちろんDTOの全てが悪いわけではない。 筆者が「冗長で脆弱」と述べたコードは Hibernateという環境においてもいくらかの価値がある。 DTOアセンブリは、ウェブ層にコントロールを返す前に、ビューが必要とするだろう全てのデータが完全にフェッチされることを保証する利点を提供する。 ウェブ層で Hibernate の LazyInitializationException と奮闘していることに気づいたなら、できうる解決はDTOパターンを試すことだ。 それは当然、すべての必要とされるデータがビジネスオブジェクトから明示的にコピーされることを要求することによって、余分な規律を押し付ける(この規律を必要とするが、読者の経験が多種多様なことに気づかない)

現在の一般的な ORM では、デタッチされた Entity の未フェッチのLazyフィールドにアクセスした場合には LazyInitializationException となります。 ORM によってはデタッチしていても再取得する実装となっているものもありますが、ティアを跨いだ場合などは壊滅的です(これを嫌ってEagerにするのは良くない考え)。

DTO を経由させることで、ビジネスロジック側でDTOが必要とするデータは全てフェッチされるため、確かに悩みは減りますが、、


結局、DTOは粗粒度のアプリケーション間のデータ転送のために存在していると言えるのかもしれない(ここでの議論は同じアプリケーションの層間でのデータ転送におけるDTO使用に焦点をあてた)。 しかしながら、この問題にはJMSもしくはSOAPの方がもっとうまく適応できるだろう。 本書のオンラインオークションアプリケーションではDTOを使わないことにする。 その代わりに、EJB層のセッションファサードがウェブ層にドメインモデルビジネスオブジェクトを返すようにしよう。

まとめ

そう言えば、こんな時代もありましたね。


タイポを修正しました。。