ネクストデザイン有限会社 [ホーム] [English]  


ドメイン駆動設計のドメインモデルからアプリケーションを生成する - Java Web

ドメイン駆動設計のドメインモデルからアプリケーションを生成する - Java Web

本ソフトウェアツールは無料・無保証です。 ダウンロードページへ
サポート とソースコード公開について
更新日


 

はじめに

本記事では、最初にドメイン駆動設計入門レベルのいくつかのポイントを整理し、 次に、ドメイン駆動設計のドメインモデルからアプリケーションを自動生成するためのソフトウェアツールについて述べます。

 

ドメイン駆動設計入門

ドメイン駆動設計は、Eric Evans氏が提唱するソフトウェア設計手法です。[Domain-driven design | DDD]

ドメイン駆動設計の基本は、ドメイン知識から導き出されたユビキタス言語とモデルです。ユーザを含む開発チームは、ドメインモデルを中心にリファクタリングを重ね、 モデルの完成度を上げ、深化させていきます。ドメイン駆動設計は、設計活動に有用なモデリングパターンや、役に立つ考察や指針を示しています。 その内容は広く深いため、最初から多くの指針やパターンを取り入れようとすると、なかなか前に踏み出せないことがあります。 まずは、ユビキタス言語とモデルの位置づけを理解するところから、小さく始めることを提案します。

モデリングの技術は奥が深く、作成したモデルに万全ということはありません。そのために繰り返し型で開発するわけです。 オブジェクト指向技術やモデル駆動設計、アジャイル開発の考え方が必要になりますが、 それらも繰り返しの中で理解を深め、レベルを上げていけばよいと考えます。


ただし、リファクタリングを効果的かつ円滑に繰り返すことは、それほど簡単ではありません。

実際、ドメインモデル以外の変更などが負担となって、思うように繰り返せないケースも少なくありません。


この記事では、対策として、ドメインモデルからアプリケーションを自動生成する仕組みを提案します。 このような仕組みは、ドメイン駆動設計との相性が良く、円滑な繰り返しに効果を発揮します。

ソフトウェアツールについて先に知りたい方は、このセクションをスキップして「ソフトウェアツールを活用する」に進んでください。

 

「エリック・エヴァンスのドメイン駆動設計」

Eric Evans氏の著書「エリック・エヴァンスのドメイン駆動設計」(以下「DDD本」と略記します) は、オブジェクト指向技術やモデル駆動アーキテクチャを多少とも学習や実践してきた技術者にとっては、 ツボを得た考察やモデリングパターンがたくさんあり、次に向かうべき方向を示してくれる心強い指南書となるでしょう。 一方、オブジェクト思考ではなく手続き思考の技術者にとっては、500頁を超える分厚いDDD本を地道に読んで理解していくのは、相当の時間を要するかもしれません。 念のためですが、オブジェクト指向言語を使っていても、オブジェクト思考になっているとは限りません。


利口なUI (アンチパターン) の罠

例えば、Struts系のフレームワークを使って作成したWebアプリケーションが、次のような構造になっているとすれば、オブジェクト思考になっているとは言えないでしょう。 DDD本に「利口なUI アンチパターン」という記述があります。 ドメイン駆動設計的にはアンチパターンですが、実際の開発では多く使われているのが現実のようです。 また、よく似たパターンにトランザクションスクリプトと呼ばれるものがあります。 トランザクションスクリプトは、マーチン・ファウラー氏の著書「エンタープライズアプリケーションアーキテクチャパターン」に示されています。 DDD本では、利口なUI とトランザクションスクリプトは違うものとされていますが、 どちらもドメイン駆動設計の考えを適用できないという意味で、ここでは、同類のアンチパターンと考えます。 下図のようなアプリケーション構造は、利口なUI やトランザクションスクリプトの例です。




上図の (A) や (B) では、JSPやアクションの中にSQL文が固定の文字列、または動的に構築される形で組込まれていて、そのSQL文で多くの業務ロジックを実現します。 そして、同じような、あるいは全く同じSQL文が、JSPやアクションの中に散在しています。 (C) ではModelと称するクラスがありますが、実態はDAOのような役割しか持ちません。 (A)(B)と同様にSQL文が重複したり散在します。 ドメイン駆動設計をはじめるに際しては、これらがアンチパターンであるという認識が必要です。


しかし、利口なUI には悩ましい利点もあります。

利口なUI の利点

※引用:ここから (同じくDDD本の第4章から引用)

単純なアプリケーションの場合、生産性が高く、すぐに作れる。

それほど有能でない開発者でも、この方法ならほとんど訓練しないで仕事ができる。

※引用:ここまで (引用元には箇条書きで他に5点示されています)


なぜ悩ましいかというと、この利点が (少数派とは言えないほどの数の) プロジェクト管理者や開発者を縛り付けていて、 オブジェクト思考へのパラダイムシフトを妨げているケースに何度も直面したからです。 大量の開発者を一時的に集めて作業をするようなスタイルの開発プロジェクトにとっては都合の良いことかもしれません。 ただし、プロジェクトの後半や保守・改修の局面で、これらの利点が効果を出しているケースは皆無に近いのではないでしょうか。 分析・設計やアーキテクチャのリファクタリング (洗練) 不足が表面化するのは、開発の初期局面ではなく後半だからです。

ちなみに、経験上「生産性が高く、すぐに作れる」というのは、初期段階だけに限られます。

本記事内の「オブジェクト指向やドメイン駆動設計を導入するときに気をつけること」を参考ください。


ドメイン駆動設計を習得し実践に移していくためには、トランザクションスクリプトの課題を理解しておくことが最低限必要かと思います。

トランザクションスクリプトの欠点については、下に引用を示しましたが、これだけではすこし分かり難いかもしれません。 しかし、トランザクションスクリプトに起因するであろう問題点を実際に経験されている方も多いのではないでしょうか。 特に開発工程の後半や、保守や改修作業において、所謂エントロピーの増大に悩まされた方は少なくないでしょう。


利口なUI の欠点

※引用:ここから (同じくDDD本の第4章から引用)

アプリケーションの統合は困難で、データベースを経由させるしかない。

ふるまいが再利用されことも、ビジネスの問題が抽象化されることもない。ビジネスルールは、適用先の操作それぞれで複製されることになる。

※引用:ここまで (引用元には箇条書きで他に2点示されています)


基本 - ユビキタス言語とモデル

DDD本の内容は広く深くボリュームがあります。ネットを検索すれば、ドメイン駆動設計に関する情報が数多く見つかります。 理解・習得するためには、オブジェクト指向技術やモデル駆動開発などの前提知識も必要になります。内容が広く深いため、理解度や習得度にも個人差が生じやすいと思います。 ここでは、ドメイン駆動設計をはじめるにあたってポイントとなる概念を、書籍「エリック・エヴァンスのドメイン駆動設計」から一部引用も含めて紹介します。


ユビキタス言語

※引用「エリック・エヴァンスのドメイン駆動設計」- 翔泳社 (ISBN 9784798121963) から:ここから

モデルを言語の骨格として使用すること。チーム内のすべてのコミュニケーションとコードにおいて、その言語を厳格に用いることを、チームに約束させること。図やドキュメント、そして何より会話の中では同一の言語を使用すること。

言語を使う上で問題があれば、代わりの表現を用いて実験することで、問題を取り除くこと。そうした表現は代りとなるモデルを反映している。そこで、新しいモデルに合わせてコードをリファクタリングし、クラス、メソッド、モジュールの名前を変更すること。会話の中で用語が混同されていたら、普通の単語の意味について認識を合わせるのと同じやり方で解決すること。

ユビキタス言語における変更は、モデルに対する変更であると認識すること。

※引用:ここまで


ドメインモデル

あるドメイン (アプリケーション領域、業務領域) におけるドメイン知識から導き出したユビキタス言語は、 そのアプリケーション開発に関わる全ての人 (開発者、ユーザ、ドメイン専門家など) たちの共通言語となります。 ユビキタス言語の語彙はドメインモデルのオブジェクト群のクラス名/オブジェクト名やメソッド名に双方向で対応付けられます。 ユビキタス言語もコード (ソフトウェアで表現されたモデル : クラス、メソッド、モジュール) も、リファクタリング (洗練) と検証を繰り返して完成度を上げ、深化させていきます。 本記事では、ドメインモデルを構成するものは、ユビキタス言語、UML図などのモデリング言語や記法で表現されたモデル、ソフトウェアで表現されたモデルと考えています。


実践的モデリング

ドメイン駆動設計では、チーム全員がモデルと実装の両方に関心を持ち、モデリングとプログラミングを行うことが推奨されています。

 

モデルと実装を常に結びつけた開発

ドメイン駆動設計では繰り返し型開発が推奨されます。リファクタリング (洗練) と検証を繰り返し、段階的にモデルの完成度を上げていく手法です。

開発プロセスの中で繰り返す単位をイテレーションと呼び、1回のイテレーションのなかで、分析から設計、実装、検証までを完結させます。 そのため、イテレーションはミニウォータフォールとも呼ばれます。


[繰り返し型開発の流れ]


本記事では大雑把に、計画から設計までを洗練と呼ぶことにします。(計画から検証までを「洗練」とすべきかもしれませんが)

各々次のような作業を行います。

[洗練 (リファクタリング)]

内容: オブジェクト指向分析・設計作業。

入力: 前のイテレーションの検証結果、今回の分析・設計で追加されたオブジェクトなど。

出力: 洗練・更新されたドメインモデル図 (UMLスケッチなど)

[実装]

内容: オブジェクト指向プログラミング作業。

入力: 洗練・更新されたドメインモデル図。

出力: Javaでオブジェクト指向プログラミングしたドメインモデルクラス。

(本記事では後述のDDBuilderを使うことを前提としますので、開発者は、エンティティ、値オブジェクト、サービスを実装します。 Webアプリケーションに必要なその他のクラスは、DDBuilderが自動生成します。)

[検証]

内容: 動くソフトウェアモデル (後述) を使ったテスト作業。

入力: 動くソフトウェアモデル。

出力: 検証結果。

※このサイトでは反復型、繰り返し型、インタラクティブ、イテレーティブという考えは特に区別なく使用しています。

 

リファクタリングと動くソフトウェアモデルを使った検証

例えば、UMLで表現されたドメインモデルは、そのままでは動かしてみることはできません。

ソフトウェアで表現されたドメインモデル (例: Javaで実装されたドメインモデル) も、実際に動かすためには単体テストコードやドライバのようなものが必要です。

ここでは「動くソフトウェアモデル」とは、「ソフトウェアで表現されたドメインモデル」のサービスやメソッドを実行できる画面を持った「Webアプリケーション」を指すこととします。

動くソフトウェアモデルは次のような観点で必要です。

  • 動くソフトウェアモデルは、各イテレーションの成果物のひとつです。
  • 実際に動かすことで、ドメインモデルの検証をより正確に、具体的にできます。
  • 開発チーム内でのドメインモデルに対する知識や理解のバラつきを抑止します。
  • 過剰な机上分析状態に陥ることを予防します。
  • ドメイン駆動設計の用語やパターン名に拘り過ぎて、理解しにくいドメインモデルになることを抑止します。
  • 技術寄りではないメンバーであっても、Webアプリケーションとして操作することで深く理解し検証できます。

などが、あげられます。

もしも、動くソフトウェアモデルを持たずに、机上のUML図や、単体テストレベルのテストコードだけでドメインモデルを検証しようとすると、やはり、 検証の深さや分かり易さという点で不足を感じるでしょう。

また、技術寄りではないメンバーにとって検証が難しくなります。

 

イテレーションを軽快にする

動くソフトウェアモデルは重要です。

ただ、現実的な視点では、イテレーションを何度も繰り返すなかで、動くソフトウェアモデルを ”いつでも動く状態” に維持することは、簡単ではありません。 例えば、ドメインモデルをWebアプリケーションで動かそうとする場合、イテレーションの度に、即ちドメインモデルを変更する度に、ビュー層や永続化層などの更新も必要になるからです。 イテレーションの本来の目的はドメインモデルのリファクタリング (洗練) です。しかし、動くソフトウェアモデルの維持に手間がかかって、簡単には繰り返せなくなることがあります。 1回1回のイテレーションが重くて手間がかかるようでは、繰り返し型の効果は得られません。

この問題を解決するためには、開発プロセスの一部を自動化するなど、何らかの対策が必要でしょう。

 

自動生成の効果

次のような効果が期待できます。
  • イテレーションを軽量化し、短いサイクルで何度でも繰り返せる。
  • リファクタリング (洗練) したドメインモデルを、素早く検証し結果を得られる。
  • 開発チームがドメイン以外の問題に煩わされないで、ドメインモデルに集中できる。
  • ドメインモデルをJavaで実装すれば、すぐに動かしてみることができるので、入門や学習にも役立ちます。

各イテレーションで何をどのように行うかは、開発の成否において重要なファクターになります。

もし、イテレーションの中でやらなくてはいけない「ドメイン以外の作業」が多いと、開発プロセスの速度と質は低下します。

例えば、イテレーションサイクルが長いと、ドメインモデルのリファクタリング (洗練) に集中できなくなる、などです。

実際、このような速度と質の低下が発生しがちです。

後述するように、以下に紹介する自動生成ツールは、ドメインモデルから「動くソフトウェアモデル」 を自動生成することで、 イテレーションに含まれるドメイン以外の問題から開発チームを開放し、開発プロセスに生じる速度や質の低下など、開発実践時の現実的な問題の解消を助けます。

【補足】ドメインモデル以外の作業とは

ドメインモデルのサービスやメソッドを検証するためのインタフェース画面やテストコードの実装作業、

ドメインモデルの永続化の実装作業、およびリファクタリング (洗練) に伴う各実装の変更作業などです。

 
 

ソフトウェアツールを活用する

ドメイン駆動設計では繰り返し型開発が推奨されます。リファクタリング (洗練) と検証を繰り返し、段階的にモデルの完成度を上げていく手法です。 繰り返し型ではイテレーションの速度と質が重要です。

 

DDBuilderとは

DDBuilderは、ドメインモデルから「動くソフトウェアモデル」を自動生成することで、イテレーションを軽量化・高速化し、 繰り返しサイクルを短くします。 開発者はドメイン以外の問題から解放され、本来のドメインに集中できるようになり、イテレーションの質を向上できます。

DDBuilderのポイントは、入力がJavaで実装されたドメインモデル、出力が「動くソフトウェアモデル」のJava Webアプリケーションであることです。

開発者はドメインモデルをJavaで実装します。

DDBuilderは、Javaドメインモデル (Javaソースコード) を読込み、Java Webアプリケーション (Javaソースコード) を生成します。

このとき、Java Webアプリケーションとしては必要だが、ドメインとは直接関係しない、あるいは、当面は関係しない、といった部分の実装を自動生成します。 要は、ドメイン以外の部分は手間いらずにするためのソフトウェアツールです。


ドメインモデルから動くソフトウェアモデルを自動生成する図

【役割】

DDBuilderはドメインモデルからWebアプリケーションを自動生成することで繰り返し (イテレーション) を軽量化します。

上図に示したように、開発チーム が設計し、Javaで実装したドメインモデル から、検証可能な動くソフトウェアモデル (Java Webアプリケーション) を生成することで、動くソフトウェアモデルの実装時間と手間を軽減します。

イテレーションの度に行っていたビュー層や永続化層の実装作業がドメイン中心になり、開発チームはドメインモデルの分析・設計・実装・評価に集中できるようになります。 単純な実装ミスも減少します。

 

成果物はドメインモデル

ただし、DDBuilderが生成するWebアプリケーションに縛られる必要はありません。

本当の成果物は、「ソフトウェアで表現されたドメインモデル」です。

ここでは、その表現手段としてJava言語、検証手段としてWebアプリケーション型を使用したにすぎません。

Webアプリケーションはそれを評価するための入れ物にすぎません。

従って、DDBuilderは、Webアプリケーション生成ツールというよりも、ドメインモデルのインキュベータ (孵化器) と考えた方がより適切です。 もちろん、そのまま運用可能なWebアプリケーションとすることもできます。

 

ソフトウェアツールを活用したイテレーションの流れ



[DDBuilderとEclipseの関係]

DDBuilderとEclipseの役割図

[1]生成/読込み更新

   生成は、初回に実行します。

   読込み更新は、[3]で更新されたドメイン実装を読取り、Webアプリケーションに反映します。

   主に、WebアプリケーションのUI層が更新されます。

   この処理は、DDBuilder画面のボタン押下で実行されます。

[2]インポート/リフレッシュ

   インポートは、初回に実行します。

   リフレッシュは[1]の更新結果をEclipseに通知・反映するために行います。

   この処理は、Eclipseの操作で行います。

[3]ドメインモデルの実装

   モデル図などをもとにEclipseでJavaプログラミングします。

   これは、開発チームのタスクです。

 

期待できる効果

  • イテレーションを軽量化(工数削減・期間短縮)できます。
  • 手組み実装によるミスとロスを排除できます。
  • 開発チームは、非本質的な作業から解放されます。
  • コードファーストで実践できます。
 

求められる知識

  • オブジェクト指向モデリング技術 (考え方)
  • ドメイン駆動設計 (考え方)
  • 繰り返し型開発 (考え方)
  • モデル駆動設計 (考え方)
  • Javaプログラミング
  • Java EE JPA(Java Persistence API)入門レベル
  • Wicket 入門レベル
  • Eclipseの基本操作
 

関連する知識

パターンという観点で簡単に区別してみます。

  • アナリシスパターンは、ドメインモデルのパターンです。
  • デザインパターンは、オブジェクト指向設計パターン、実装パターンです。          
  • ドメイン駆動設計は、モデリングのパターンです。
 

ユーザガイド

ユーザガイド [PDF][2.5MB]


 

入門・学習に活用する

DDBuilderは、実際の開発プロセスだけではなく、入門や練習にも効果的です。 例えば、実際にドメインモデルをJavaで実装してみて、アプリケーションとしての振る舞いを、あまり手間をかけずに、具体的に試してみることができます。 そうすれば、トランザクションスクリプトとの違いを具体的に見て頂けると思いますし、ドメイン駆動設計のドメインモデルとは何かも、ざっと見てみることができます。

また、トランザクションスクリプトの利点とされている 開発着手時の生産性の問題の解消策にもなります。

もちろん前提として、ドメインモデルの位置付けや役割についての (ある程度の) 理解や、オブジェクト指向によるモデリングやプログラミングのスキルは必要ですが、 まずは、実際に試してみることも効果的ではないでしょうか。

オブジェクト指向、モデル駆動設計、アジャイル開発、ドメイン駆動設計などを、これから始めるという入門者にとって、 文献だけでドメインモデルを理解したり、イメージしたりすることは、難しく、時間がかかるかもしれません。 実際に実装し動かしてみることができれば、理解もしやすいと考えます。

ドメイン駆動設計が示す内容は広く深いです。 しかし、その (少し深い) 内容に拘りすぎて、実際に何も手を動かせないといった状況も、とても残念な状況です。 例えば、オブジェクト指向の基礎とDDD本の第5章位までを読み、 後は、実際に試しながら理解を深めていくといった方法も検討してみてはいかがでしょうか。

書籍など

・「エリック・エヴァンスのドメイン駆動設計」- 翔泳社

・「Domain-Driven Design Quickly 日本語版」


動作確認済みの環境

  • Java 8
  • Windows7 pro 32bit/64bit, Windows10 pro 64bit
  • Eclipse IDE for Java EE Developers 4.4, 4.3
  • Apache Tomcat 7.0, 8.0, 8.5, 9.0

インストールとアンインストール

DDBuilderはスタンドアロンのJavaデスクトップアプリケーションです。

インストールは、ダウンロードしたzipファイルを適当な場所に解凍してください。

アンインストールは、 解凍結果をエクスプローラなどで削除してください。

※ ダウンロードしたファイルを解凍すると次の説明ファイルが含まれていますので、参考にしてください。

「はじめにお読みください(使い方).txt」

「DDBuilder_Guide_Ver2.pdf」

ダウンロードページへ


謝辞

DDBuilderは次のソフトウェアを利用しています。

サポートとソースコード公開について

使いはじめで、ご不明な点がありましたら お問合せフォーム からご連絡ください。

なお、弊社の都合で返信が遅くなる場合があります。

また、有料でのサポートも用意しております。

サポート内容や形態などは柔軟に対応できますので、 お問合フォーム からご連絡ください。

DDBuilderのソースコードは、準備が整い次第、公開する方向です。


Java 関連ツール

Jクラスレポート Javaソースからメトリクス抽出・レポート作成



参考例



例1 レイヤ化アーキテクチャ

DDBuilderが自動生成する Web アプリケーションの構成図です。

DDD本に示されているレイヤ化アーキテクチャを参考にしています。

「開発者作成」の部分は、開発者によって、ドメイン駆動設計でモデリングされ、Javaで実装されたものになります。DDBuilderはこのJavaクラス群を読み込みビュー層などを生成します。


自動生成するアプリケーション構造図


DDBuilderが生成する「動くソフトウェア」は、Java Web アプリケーションです。

Eclipseのプロジェクト形式になっていますので、Eclipseにインポートして、編集・実行できます。

DDBuilder固有のコンテナなどはなく、フレームワークとして次を使用しています。

ビュー層: Apache Wicket

永続化層: Java EE JPA/ Hibernate

※上図中の「DDBuilder基底」部分に、いくつかの簡単な基底クラスを含んでいます。


ロックインフリー

利用者チームは、開発時でも実行時でも、いつでもDDBuilderを切り離すことができます。

DDBuilderにロックインされることはありません。

ドメインモデルがある程度安定し、頻繁な繰り返しが必要ない段階になれば、DDBuilderは必要なくなるかもしれません。

そして、ドメインモデルを他の言語やアプリケーションフレームワークに移植することもあるでしょう。

DDBuilderはドメインモデルのインキュベータ (孵化器・育成器) です。

もちろん、そのまま運用することもできます。


自作ページの追加

自動生成されたWebアプリケーションに自作のページを追加できます。

DDBuilderは自作ページを上書きしません。

自作ページの作成は、自動生成されたページクラスを参考にすれば、効率よく自作できるでしょう。


Java を採用

リファクタリングの中で、クラス名を変えたり、メソッド名を変えることは頻繁にあります。 Javaのような強い静的型付けの言語であれば、Eclipseなどのリファクタリング機能で期待通りに簡単に変更できます。 しかし (筆者の勉強不足かもしれませんが) 動的型付け言語では期待する結果を得られないのではないでしょうか。 予期せぬ名前まで置換えられたりしないでしょうか。


Apache Wicket を採用

Wicketは、Java Webアプリケーションフレームワークです。

Struts系と違い、 オブジェクト指向プログラミングモデルを前提としたフレームワークです。

HTMLとの独立性が高く、基本的にすべてをJavaコードで実装するアーキテクチャは、 自動生成機能を実現するうえでも好都合で相性がよいと言えます。

ドメインモデルは利用者が実装しますが、Wicketを意識することはありません。

Wicketを意識するのは自作ページを追加する場合です。


Java JPA Hibernate を採用

HibernateはJavaEE JPA実装の1つです。

DDBuilderが生成する動くソフトウェアは、デフォルトではJava標準のJavaDBを使用します。

Hibernateは、 PostgreSQL, MySQL, Oracle, SQLServer, DB2など代表的なデータベースをサポートしており、設定変更だけで他のデータベースに変更できます。

動くソフトウェアは、Hibernateを「Javaアプリケーションのスタンドアロン型」で使用し、Springなどのコンテナは前提としません。

ドメインモデル層のクラスを実装するときには、JPA / Hibernateの知識が必要になります。

主に、関連を定義するためのアノテーションの書き方などの知識が必要になります。


ダウンロードページへ

[ 戻る]



例2 クラス図の扱いについて

DDBuilderにはクラス図をサポートする機能はありませんが、ドメイン駆動設計を実践するうえで、クラス図やそれに相当するものは必要です。

本記事では、分析時のクラス図は手書きラフスケッチとし、正式なクラス図はUMLツールなどで Java ソースからリバースエンジニアリングされることを推奨します。リバースエンジニアリングであれば常に実装と一致させることができます。もしも、表計算ソフトなどで作成すると不一致が起き、イテレーションが回らなくなる原因にもなると思います。

また当社では、

JavaクラスレポートというJavaソースからメトリクスなどの情報抽出を行うツールも公開[無料]していますのでご参考ください。


[ 戻る]



例3 オブジェクト指向やドメイン駆動設計を導入するときに気をつけること

プロジェクトをドメイン駆動設計やオブジェクト指向で始めても途中で断念したり、方針変更を余儀なくされた、という経験はないでしょうか。

そのような結果を生む原因の1つに、下図に示すような「立ち上がり期」の停滞感があると思います。 そのために、オブジェクト指向やドメイン駆動設計で始めたけれど、進捗が遅い、先が不安などと判断され、過去の手法に戻すことになったりします。 原因としては、パターン名や用語に振り回されて空転しているようなケースもありますが、やはり、トランザクションスクリプト型などの方が、とりあえず何らかの進捗が出やすいというのも事実でしょう。


導入時の立ち上がりの比較


※注意:上のグラフは感覚的なものです。

この立ち上がり期を乗り越えないと、その効果が見える前に従来型に戻されてしまいます。この停滞感を、手法に対する想いや熱意だけで乗り越えるのも難しいでしょう。


DDBuilderを利用することで、この停滞感を少しでも解消できればと思います。

机上だけではなく、実際に動くソフトウェアを素早く試すことができれば、プロジェクトに存在する不安を払しょくし、技術やドメイン知識の習得スピードも上がると思います。


DDD本には考えるべきことが多く書かれています。しかし、最初から多くの指針やパターンを取り入れようとすると、なかなか前には進まないでしょう。 ソフトウェア設計は最終的には自分流です。ただし基本を学んだ上の自分流と、まったくの我流は違います。 DDD本の少しだけでも実践できれば、実践する前に比べて、遥かに良いものになると思います。


ダウンロードページへ

[ 戻る]



例4 ドメインモデル (エンティティ、値オブジェクト、サービス) とDDBuilderを活用したイテレーション

ドメイン駆動設計では繰り返し型開発が推奨されます。

繰り返し型開発プロセスの最も重要な要素はイテレーションです。

イテレーションに自動生成ツール DDBuilder を利用したイテレーションの流れを具体的に示します。

ここでは、ドメインとして在庫管理業務を想定して、倉庫、製品、在庫、在庫管理サービスといったドメインモデルを作り上げていく流れを説明します。

すこし長くなりますので、まず、説明 (概要) を述べた後で、説明 (詳細) を続けます。

すでに上で述べました「 ソフトウェアツールを活用したイテレーションの流れ」も参考にしてください。         

要は、

・ ドメインモデルを作成・修正 (Javaプログラミング)

・ DDBuilderで更新 (自動生成/更新)

・ Eclipseで検証

の繰り返しです。

では、説明を始めます。


説明 (概要)

STEP1 DDBuilderをインストール

・ 以下の通りにDDBuilderをPCにインストールします。

ダウンロードページからダウンロードしてください。

・ ダウンロードしたファイルを適当な場所に解凍します。

・ 解凍すると次の説明ファイルが含まれていますので、参考にしてください。

   「はじめにお読みください(使い方).txt」、「DDBuilder_Guide_Ver2.pdf」


STEP2 インストールの確認

・ 正しくインストールされたことを確認するために、DDBuilderを起動します。

・ 作成するWebアプリケーションの名前などの基本情報を設定します。

・ Webアプリケーションを生成してみます。 (自分たちが作成したドメインクラスが1つもない状態)

・ 生成されたWebアプリケーションをEclipseにインポートします。

・ Eclipse上で (動的Webプロジェクト) Webアプリケーションを実行します。

・ ブラウザで動作を確認します。


STEP3 1回目のイテレーション

・ 最初のドメインクラスとして「製品」クラスをEclipseで1つだけ作成します。

・ DDBuilderでWebアプリケーションを更新します。

   (DDBuilderは「製品」クラス (Javaソースファイル) などを読み込みWebアプリケーションを更新します)

・ WclipseからWebアプリケーションプロジェクトをリフレッシュします。

   (EclipseにDDBuilderが行った変更を通知するため)

・ Eclipse上でWebアプリケーションを実行します。

・ ブラウザから動作を確認します。

・ Eclipseの準備方法は他のサイトをご参考ください。


STEP4 2回目のイテレーション

・ 次に倉庫、製品、在庫、在庫管理サービスをEclipseで作成します。

・ Eclipse上でWebアプリケーションを実行します。

・ ブラウザから動作を確認します。


説明 (詳細)

まず、DDBuilderを起動すると下図の画面が表示されます。

※DDBuilderはJavaで作成されたスタンドアロンアプリケーションです。Eclipseのプラグインではありません。Eclipseとは関係なく独立して動作します。

DDBuilderを最初に起動したときの画面


起動したらまず、Webアプリケーションの作成場所やアプリケーション名などの基本情報を入力します。

この基本情報は、生成されたJava Web アプリケーションの画面ラベル等にも反映されます。

より詳細な設定は、「他の設定」ボタンを押してから設定してください。

下図は入力例です。

生成するWebアプリケーションの基本情報の入力例


基本情報を入力し、作成/更新ボタンを押下するとWebアプリケーションの基本形が作成されます。

この時点ではドメインモデルはひとつも定義されていませんので、

生成されたWebアプリケーションは最小限の画面 (トップ画面など) を含むだけです。

次に、生成されたWebアプリケーションを編集できるように、Eclipseにインポートします。

ここでの編集とは、ドメインモデルを Java で実装したり、Web アプリケーションとしてデバッグ実行して、ドメインモデルを検証することを指します。

※Webアプリケーションは、インポート可能なEclipseプロジェクトとして生成されています。

Eclipse画面例


インポートが完了すると、上図のように「プロジェクトエクスプローラ」ビューに表示されます。

確認のために、EclipseでWebアプリケーションを実行してみます。

この時点では、ドメインモデルは1つもありませんが、基本的な画面遷移の動作確認はできます。

下図はEclipse内で実行した時の例です。

デバッグ実行画面


上図はEclipse内蔵のブラウザで表示された状態ですが、Eclipse外のブラウザからもアクセスしてテストできます。

下図はChromeからアクセスした例です。例:http://localhost:8080/zaiko/

Chromeブラウザでアクセスした例


ここまでで、Webアプリケーションの基本動作ができました。(ドメインモデルが無い空のアプリケーション)

この先は、ドメイン駆動設計による分析、ドメインモデルの抽出、設計、実装、検証する作業の繰り返しが始まります。

つまり、初回のイテレーションの開始です。

一気にドメイン全体をカバーしようとするのではなく、1週間程度で行える範囲や優先度の高いユースケースが通る範囲などをイテレーションのゴールとして設定します。(1週間が妥当かどうかは要検討です)

期間の目安や、ユースケースの優先度などはプロジェクトによって違ってくるでしょう。

繰り返し型開発プロセスやアジャイルなどの手法解説を参考にしてください。


まず、もっとも簡単な例として、下図のProductオブジェクトを1つだけ作成してみます。

つまり、イテレーションのゴールは、以下の製品Productクラスの初版を実装し、Webアプリケーションの形で振る舞いを検証することです。


エンティティ

単純なドメインモデルの例


/**
 * 製品
 */
@Entity     <------------- JPAアノテーション
public class Product extends DdBaseEntity {   <------------- DDBuilderエンティティ基底クラス
    private static final long serialVersionUID = 1L;
    /**
     * 製品名
     */
    private String name;
    /**
     * コンストラクタ
     */
    public Product(){
        super();
        this.name = "";
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

実装作業は、下図のように、EclipseでProductクラスを実装します。

domainパッケージの直下にProduct.javaを追加します。サブパッケージを追加することもできます。

Product.javaを作成する例


これで、Productクラスは追加できました。

ただし、Productクラスのインスタンスを登録したり、表示したりするためのビュー層や永続化層はありません。

これらは、DDBuilderが自動生成します。

そのために、DDBuilderでWebアプリケーションを更新します。

DDBuilderはProduct.javaファイルを読み込み、ビュー層のクラスなどを追加します。v

※なお、前回入力されたDDBuilder画面の基本情報は維持されています。


作成/更新ボタンを押下し、完了ダイアログが表示されたら、Webアプリケーションの中に必要なjavaファイルが追加されています。しかし、Eclipseは、追加されたファイルを感知していません。そのために必ず、Eclipse側でプロジェクトをリフレッシュしてください。(忘れがちです)

リフレッシュしたら、再度Webアプリケーションを起動してテストします。

下図はChromeからアクセスした例です。

動くソフトウェアを実行し、ブラウザからアクセスしたトップ画面の例


ここで、ドメインクラス一覧ボタンをクリックすると次画面に遷移します。

ドメインクラス一覧画面の例


次に、製品Productリンクをクリックすると次画面に遷移します。

インスタンス一覧画面の例


次に、新規作成リンクをクリックすると次画面に遷移します。

インスタンスの編集画面例


次に、製品名を入力し保存ボタンを押下すると、Productインスタンスが永続化され、次画面に遷移します。

エンティティインスタンスの一覧画面の例


1件登録され、一覧に追加されています。

次に、編集リンクをクリックすると次画面に遷移します。

エンティティの編集画面の例


次に、製品名を変更すると次画面に遷移します。

ブラウザからアクセスしたエンティティ編集画面の例


次に、保存すると次画面に遷移します。

ブラウザからアクセスしたエンティティ一覧画面例


ここまではProductクラスだけでしたが、下図のようにドメインモデルに1つのサービスと2つのエンティティを追加します。

値オブジェクト (バリューオブジェクト) はこの例では登場しません。


サンプルとして使用するドメインモデルの例


Productを追加した時と同様の手順で、Webアプリケーションを更新し、テスト実行します。

動くソフトウェアのトップ画面


ドメインクラス一覧ボタン → 製品Productリンクをクリックします。

なお、ここでは説明を省略しますが、以降の説明での中では、下図のようにテストデータが作成済みとします。

テストデータの作成については ユーガイド(PDF)を参照ください。

ドメインクラス一覧ボタンを押下します。

ドメインクラスの一覧画面


製品 Productリンクをクリックします。

インスタンスの一覧画面


例として、2行目の編集リンクをクリックします。

インスタンスの編集画面


編集して保存するか、キャンセルで戻ります。

次に、サービスメソッドを実行してみます。

このサービスは、ある倉庫から別の倉庫に在庫を移動させる業務を実現します。

サービス一覧またはホーム → サービス一覧で下図の画面が表示されます。

サービスメソッドの一覧画面


実行リンクをクリックします。

下図の画面が表示されたら、このサービスメソッドに渡す引数を指定します。

from移動元倉庫:選択ボタンを押下します。

サービスメソッドの実行画面


例として「福岡センター」を選択します。

サービスメソッドの引数設定画面(1)


仮決定ボタンを押下します。

サービスメソッドの引数設定画面(2)


同様に移動先の倉庫と移動する製品と数量を指定します。

サービスメソッドの引数設定画面(3)


サービス実行を押下するとお下図の完了画面が表示されます。

倉庫間移動メソッドの詳細は実装していませんので、戻り値としてtruetrueが表示されています。

サービスメソッドの実行結果画面(4)


実装コード(例:StockService, Warehouse, Stock)


package jp.co.nextdesign.domain;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import javax.persistence.Transient;
import jp.co.nextdesign.domain.ddb.DdBaseEntity;
/**
 * 製品
 */
@Entity
public class Product extends DdBaseEntity {
    private static final long serialVersionUID = 1L;

    /** 製品名 */
    private String name;
    
    /** 在庫 */
    @OneToMany(mappedBy="product", cascade=CascadeType.ALL, orphanRemoval=true)
    private List<Stock> stockList = new ArrayList<Stock>();
    
    /** コンストラクタ */
    public Product(){
        super();
        this.name = "";
    }

    //OneToManyで双方向関連を維持するためのコードを
//含むgetStockList(),setStockList(List<Stock> stockList)の例
    @Transient
    private ArrayList<Stock> latestStockList = new ArrayList<Stock>();
    public List<Stock> getStockList() {
        return this.stockList;
    }
    public void setStockList(List<Stock> stockList) {
        for(Stock newStock : stockList){
            if (!latestStockList.contains(newStock)){
                newStock.setProduct(this);
            }
        }
        for(Stock oldStock : latestStockList){
            if (!stockList.contains(oldStock)){
                oldStock.setProduct(null);
            }
        }
        this.stockList = stockList;
        latestStockList = new ArrayList<Stock>(this.stockList);
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    
    /** DDB一覧表示用タイトル */
    @Override
    public String getDDBEntityTitle(){
        return "製品名:" + this.getName();
    }
}

package jp.co.nextdesign.domain;
import java.util.Calendar;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import jp.co.nextdesign.domain.ddb.DdBaseEntity;
/**
 * 在庫
 */
@Entity
public class Stock extends DdBaseEntity {
    private static final long serialVersionUID = 1L;
        
    /** 数量 */
    private Integer quantity;
        
    /** 製品 */
    @ManyToOne
    private Product product;
        
    /** 倉庫  */
    @ManyToOne
    private Warehouse warehouse;
        
    /** コンストラクタ */
    public Stock(){
        super();
        this.quantity = 0;
    }
        
    /**
     * 次月の入庫予定日を応答する
     * @return 次月の入庫予定日
     */
    public Date getNextMonthWarehousingDate(){
        Date result = Calendar.getInstance().getTime();
        //何らかの実装
        return result;
    }
        
    /** DDB一覧表示用タイトル */
    @Override
    public String getDDBEntityTitle(){
        String result = "製品名=";
        result += this.getProduct() != null ? this.getProduct().getName() : "";
        result += " 倉庫名=";
        result += this.getWarehouse() != null ? this.getWarehouse().getName() : "";
        result += " 数量=" + this.getQuantity();
        return result;
    }
    public Integer getQuantity() {
        return quantity;
    }
    public void setQuantity(Integer quantity) {
        this.quantity = quantity;
    }
    public Product getProduct() {
        return product;
    }
    public void setProduct(Product product) {
        this.product = product;
    }
    public Warehouse getWarehouse() {
        return warehouse;
    }
    public void setWarehouse(Warehouse warehouse) {
        this.warehouse = warehouse;
    }       
}

package jp.co.nextdesign.domain;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import javax.persistence.Transient;
import jp.co.nextdesign.domain.ddb.DdBaseEntity;
/**
 * 倉庫
 */
@Entity
public class Warehouse extends DdBaseEntity {
    private static final long serialVersionUID = 1L;

    /** 倉庫名 */
    private String name;
    
    /** 在庫リスト */
    @OneToMany(mappedBy="warehouse", cascade=CascadeType.ALL, orphanRemoval=true)
    private List<Stock> stockList = new ArrayList<Stock>();

    /** コンストラクタ */
    public Warehouse(){
        super();
        this.name = "";
    }
    
    /** 製品在庫を追加する */
    public boolean addStock(Product product, int quantity){
        //処理(この例では省略)
        return true;
    }

    /** 製品在庫を削減する */
    public boolean removeStock(Product product, int quantity){
        //処理(この例では省略)
        return true;
    }

    //OneToManyで双方向関連を維持するためのコードを
//含むgetStockList(),setStockList(List<Stock> stockList)の例
    @Transient
    private ArrayList<Stock> latestStockList = new ArrayList<Stock>();
    public List<Stock> getStockList() {
        return this.stockList;
    }
    public void setStockList(List<Stock> stockList) {
        for(Stock newStock : stockList){
            if (!latestStockList.contains(newStock)){
                newStock.setWarehouse(this);
            }
        }
        for(Stock oldStock : latestStockList){
            if (!stockList.contains(oldStock)){
                oldStock.setWarehouse(null);
            }
        }
        this.stockList = stockList;
        latestStockList = new ArrayList<Stock>(this.stockList);
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    
    /** DDB一覧表示用タイトル */
    @Override
    public String getDDBEntityTitle(){
        return "倉庫名:" + this.getName();
    }
}

package jp.co.nextdesign.service;
import jp.co.nextdesign.domain.Product;
import jp.co.nextdesign.domain.Warehouse;
import jp.co.nextdesign.service.ddb.DdBaseService;
/**
 * 在庫管理サービス
 */
public class StockService extends DdBaseService {

    /**
     * 倉庫間移動
     * @param from 移動元倉庫
     * @param to 移動先倉庫
     * @param product 移動する商品
     * @param quantity 移動する数量
     * @return
     */
    public Boolean transferStock(Warehouse from, Warehouse to, Product product, Integer quantity) {
        boolean result = false;
        try {
            startService(); //サービス初期化処理
            begin(); //トランザクション開始
            
            // 倉庫間移動処理
            if (from.removeStock(product, quantity)){
                if (to.addStock(product, quantity)){
                    result = true;
                }
            }
            if (result){
                commit(); //トランザクションcommit
            } else {
                rollback(); //トランザクションrollback
            }
        } catch (Exception e) {
            rollback();
        } finally {
            endService(); //サービス終了処理
        }
        return result;
    }
}

詳細は ユーザガイド(PDF)をご覧ください。

ダウンロードページへ

[ 戻る]



例5 DDBuilderがサポートするドメインモデルの関連タイプと属性型

このドメインモデルは、DDBuilderがサポートする関連の種類、インスタンス属性型の種類を確認するためのサンプルです。 ドメイン駆動設計的にはあまり参考になりません。 [ コードをダウンロード]


UMLクラス図

ドメインモデルの例


Javaコード


/*
 * DDBuilderで使える関連タイプと属性型に関する例です。ドメイン駆動設計的な観点の例ではありません。
 */
package jp.co.nextdesign.domain;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import javax.persistence.Transient;
import jp.co.nextdesign.domain.ddb.DdBaseEntity;

/**
 * 著者
 */
@Entity
public class Author extends DdBaseEntity {
    private static final long serialVersionUID = 1L;

    /** 名前 */
    private String name;
    
    /** 書籍リスト owning/parent */
    @OneToMany(mappedBy="author", cascade=CascadeType.ALL, orphanRemoval=true)
    private List<Book> bookList;

    /** 書籍リスト owning/parent */
    @OneToMany(mappedBy="author2", cascade=CascadeType.ALL, orphanRemoval=true)
    private List<Book> bookList2;

    /** コンストラクタ */
    public Author(){
        super();
        this.name = "---";
        this.bookList = new ArrayList<Book>();
        this.bookList2 = new ArrayList<Book>();
    }
    
    //OneToManyで双方向関連を維持するためのコードを含むgetBookList(),
    //setBookList(List<Book> bookList)の例
    @Transient
    private ArrayList<Book> latestBookList = new ArrayList<Book>();
    public List<Book> getBookList() {
        return this.bookList;
    }
    public void setBookList(List<Book> bookList) {
        for(Book newBook : bookList){
            if (!latestBookList.contains(newBook)){
                newBook.setAuthor(this);
            }
        }
        for(Book oldBook : latestBookList){
            if (!bookList.contains(oldBook)){
                oldBook.setAuthor(null);
            }
        }
        this.bookList = bookList;
        latestBookList = new ArrayList<Book>(this.bookList);
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public List<Book> getBookList2() {
        return bookList2;
    }
    public void setBookList2(List<Book> bookList2) {
        this.bookList2 = bookList2;
    }
    
    @Override
    public String getDDBEntityTitle(){
        return this.name;
    }
    
    /** debug */
    public String getDebugInfo(){
        String info = "<" + this.getClass().getSimpleName() + ">";
        info += "\nname=" + this.getName();
        info += "\n</" + this.getClass().getSimpleName() + ">";
        return info;
    }
}

/*
 * ドメインモデル例
 */
package jp.co.nextdesign.domain;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Transient;
import jp.co.nextdesign.domain.ddb.DdBaseEntity;
import jp.co.nextdesign.domain.store.Store;

/**
 * 書籍
 */
@Entity
public class Book extends DdBaseEntity {
    private static final long serialVersionUID = 1L;
    
    /** 書名 */
    private String name;

    /** 書名2 */
    private String name2;

    /** 出版日 */
    private Date publishedAt;
    
    /** 出版日2 */
    private Date publishedAt2;
    
    /** 仕入価格 */
    private BigDecimal cost;
    
    /** 仕入価格2 */
    private BigDecimal cost2;
    
    /** キャンペーン1 isなし */
    private Boolean campaign1;
    
    /** キャンペーン12 isなし */
    private Boolean campaign12;
    
    /** キャンペーン2 is付き */
    private Boolean isCampaign2;
    
    /** キャンペーン22 is付き */
    private Boolean isCampaign22;
    
    /** 言語 */
    @Enumerated(EnumType.STRING)
    private EnumLanguage attEnum;
    
    /** 言語2 */
    @Enumerated(EnumType.STRING)
    private EnumLanguage attEnum2;

    /** ISBN one-to-one owning/parent Hibernate ORM 5.2 User Guide2.7と異なる */
    @OneToOne(cascade=CascadeType.ALL)
    @JoinColumn(name="isbn_id") //自分の列
    private Isbn isbn;
    
    /** ISBN2 one-to-one owning/parent Hibernate ORM 5.2 User Guide2.7と異なる */
    @OneToOne(cascade=CascadeType.ALL)
    @JoinColumn(name="isbn_id2") //自分の列
    private Isbn isbn2;
    
    /** 著者 */
    @ManyToOne
    private Author author;

    /** 著者2 */
    @ManyToOne
    private Author author2;

    /** 版 */
    @OneToMany(mappedBy="book", cascade=CascadeType.ALL, orphanRemoval=true)
    private List<Edition> editionList;
    
    /** 版2 */
    @OneToMany(mappedBy="book2", cascade=CascadeType.ALL, orphanRemoval=true)
    private List<Edition> editionList2;
    
    /**
     * 書店
     * Book編集画面で関連付ける書店を選択/解除しても書店側には反映されない。
     * setStoreList, getStoreListを参照。
     * 同期反映させるにはaddStore/removeStoreを使うかまたは、ManyToManyを
     * 2つのOneToManyで定義する。
     * Hibernate ORM 5.2 User Guide2.7.2 Bidirectional ManyToMany参照
     */
    @ManyToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE}, fetch=FetchType.EAGER)
    private List<Store> storeList;
    
    /** 書店2
     * BookとStoreの間に2つのManyToManyを定義すると、
     * 次のようなBook_Storeテーブルが作成される。
     * このテーブルにinsertするためには、4つのxxxx_idが全てnot nullに限られるので、
     * insert時に(常に)例外が発生する。
     * 同じエンティティ間で複数のMantToMany関連を定義したい場合は
     * @JoinTableを使用する必要があると思われる。
     * ここでは未確認。
     * create table Book_Store (
     *   bookList2_id bigint not null,
     *   storeList2_id bigint not null,
     *   bookList_id bigint not null,
     *   storeList_id bigint not null
     * )
     */
//    @ManyToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE})
//    private List<Store> storeList2;
    
    /** Integer型属性名 */
    private Integer integerAttribute;
    
    /** Integer型属性名2 */
    private Integer integerAttribute2;
    
    /** Byte型属性名 */
    private Byte attByte;
    
    /** Byte型属性名2 */
    private Byte attByte2;
    
    /** Short型属性名 */
    private Short attShort;
    
    /** Short型属性名2 */
    private Short attShort2;
    
    /** Long型属性名 */
    private Long attLong;
    
    /** Long型属性名2 */
    private Long attLong2;
    
    /** Float型属性名 */
    private Float attFloat;
    
    /** Float型属性名2 */
    private Float attFloat2;
    
    /** Double型属性名 */
    private Double attDouble;
    
    /** Double型属性名2 */
    private Double attDouble2;
    
    /** Character型属性名 */
    private Character attCharacter;
    
    /** Character型属性名2 */
    private Character attCharacter2;
    
    /** コンストラクタ */
    public Book(){
        super();
        this.name = "";
        this.storeList = new ArrayList<Store>();
//        this.storeList2 = new ArrayList<Store>();
        this.editionList = new ArrayList<Edition>();
        this.editionList2 = new ArrayList<Edition>();
    }
    
    //ManyToManyで双方向関連を維持するためのaddStore,removeStoreを含む。
    //owning側ではなくmappedBy側から使用するが、両側に実装する。
    public List<Store> getStoreList() {
        return storeList;
    }
    public void setStoreList(List<Store> storeList) {
        this.storeList = storeList;
    }
    public void addStore(Store store){
        if (store != null && !this.storeList.contains(store)){
            this.storeList.add(store);
            store.addBook(this);
        }
    }
    public void removeStore(Store store){
        if (store != null && this.storeList.contains(store)){
            this.storeList.remove(store);
            store.removeBook(this);
        }
    }
    
    //OneToManyで双方向関連を維持するためのコードを含むgetEditionList(),
    //setEditionList(List<Edition> editionList)の例
    @Transient
    private ArrayList<Edition> latestEditionList = new ArrayList<Edition>();
    public List<Edition> getEditionList() {
        return this.editionList;
    }
    public void setEditionList(List<Edition> editionList) {
        for(Edition newEdition : editionList){
            if (!latestEditionList.contains(newEdition)){
                newEdition.setBook(this);
            }
        }
        for(Edition oldEdition : latestEditionList){
            if (!editionList.contains(oldEdition)){
                oldEdition.setBook(null);
            }
        }
        this.editionList = editionList;
        latestEditionList = new ArrayList<Edition>(this.editionList);
    }
    
    /** DDBのviewが使用する */
    @Override
    public String getDDBEntityTitle(){
        String result = this.getName();
        result += this.getAuthor() != null ? this.getAuthor().getName() : "";
        return result;
    }
    
//    public List<Store> getStoreList2() {
//        return storeList2;
//    }
//    public void setStoreList2(List<Store> storeList2) {
//        this.storeList2 = storeList2;
//    }
    public Isbn getIsbn() {
        return isbn;
    }
    public void setIsbn(Isbn isbn) {
        this.isbn = isbn;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Author getAuthor() {
        return author;
    }
    public void setAuthor(Author author) {
        this.author = author;
    }
    public Date getPublishedAt() {
        return publishedAt;
    }
    public void setPublishedAt(Date publishedAt) {
        this.publishedAt = publishedAt;
    }
    //Eclipseのgetter/setter自動生成では(booleanではなく
    //Booleanの場合)is名前形式のgetterは生成されない。
    //Wicket はis名前形式のgetter以外にget名前形式でもよい。
    public Boolean getCampaign1() {
        return campaign1;
    }
    public void setCampaign1(Boolean campaign1) {
        this.campaign1 = campaign1;
    }
    public Boolean getIsCampaign2() {
        return isCampaign2;
    }
    public void setIsCampaign2(Boolean isCampaign2) {
        this.isCampaign2 = isCampaign2;
    }
    public Byte getAttByte() {
        return attByte;
    }
    public void setAttByte(Byte attByte) {
        this.attByte = attByte;
    }
    public Short getAttShort() {
        return attShort;
    }
    public void setAttShort(Short attShort) {
        this.attShort = attShort;
    }
    public Integer getIntegerAttribute() {
        return integerAttribute;
    }
    public void setIntegerAttribute(Integer integerAttribute) {
        this.integerAttribute = integerAttribute;
    }
    public Long getAttLong() {
        return attLong;
    }
    public void setAttLong(Long attLong) {
        this.attLong = attLong;
    }
    public Float getAttFloat() {
        return attFloat;
    }
    public void setAttFloat(Float attFloat) {
        this.attFloat = attFloat;
    }
    public Double getAttDouble() {
        return attDouble;
    }
    public void setAttDouble(Double attDouble) {
        this.attDouble = attDouble;
    }
    public Character getAttCharacter() {
        return attCharacter;
    }
    public void setAttCharacter(Character attCharacter) {
        this.attCharacter = attCharacter;
    }
    public EnumLanguage getAttEnum() {
        return attEnum;
    }
    public void setAttEnum(EnumLanguage attEnum) {
        this.attEnum = attEnum;
    }

    /** debug用 */
    public void debugPrint(){
        String info = "<" + this.getClass().getSimpleName() + ">";
        info += "\nname=" + this.getName();
        info += "\npublishedAt=" + this.getPublishedAt();
        if(this.author != null) info += "\n" + this.getAuthor().getDebugInfo();
        if(this.isbn != null) info += "\n" + this.getIsbn().getDebugInfo();
        for(Edition edition : this.getEditionList()){
            info += "\n" + edition.getDebugInfo();
        }
        for(Store bookStore : this.getStoreList()){
            info += "\n" + bookStore.getDebugInfo();
        }
        info += "\n</" + this.getClass().getSimpleName() + ">";
        System.out.println("--------------------------------------");
        System.out.println(info);
        System.out.println("--------------------------------------");
    }

    public String getName2() {
        return name2;
    }
    public void setName2(String name2) {
        this.name2 = name2;
    }
    public Date getPublishedAt2() {
        return publishedAt2;
    }
    public void setPublishedAt2(Date publishedAt2) {
        this.publishedAt2 = publishedAt2;
    }
    public Boolean getCampaign12() {
        return campaign12;
    }
    public void setCampaign12(Boolean campaign12) {
        this.campaign12 = campaign12;
    }
    public Boolean getIsCampaign22() {
        return isCampaign22;
    }
    public void setIsCampaign22(Boolean isCampaign22) {
        this.isCampaign22 = isCampaign22;
    }
    public EnumLanguage getAttEnum2() {
        return attEnum2;
    }
    public void setAttEnum2(EnumLanguage attEnum2) {
        this.attEnum2 = attEnum2;
    }
    public Isbn getIsbn2() {
        return isbn2;
    }
    public void setIsbn2(Isbn isbn2) {
        this.isbn2 = isbn2;
    }
    public Author getAuthor2() {
        return author2;
    }
    public void setAuthor2(Author author2) {
        this.author2 = author2;
    }
    public List<Edition> getEditionList2() {
        return editionList2;
    }
    public void setEditionList2(List<Edition> editionList2) {
        this.editionList2 = editionList2;
    }
//    public List<Store> getStoreList2() {
//        return storeList2;
//    }
//    public void setStoreList2(List<Store> storeList2) {
//        this.storeList2 = storeList2;
//    }
    public Integer getIntegerAttribute2() {
        return integerAttribute2;
    }
    public void setIntegerAttribute2(Integer integerAttribute2) {
        this.integerAttribute2 = integerAttribute2;
    }
    public Byte getAttByte2() {
        return attByte2;
    }
    public void setAttByte2(Byte attByte2) {
        this.attByte2 = attByte2;
    }
    public Short getAttShort2() {
        return attShort2;
    }
    public void setAttShort2(Short attShort2) {
        this.attShort2 = attShort2;
    }
    public Long getAttLong2() {
        return attLong2;
    }
    public void setAttLong2(Long attLong2) {
        this.attLong2 = attLong2;
    }
    public Float getAttFloat2() {
        return attFloat2;
    }
    public void setAttFloat2(Float attFloat2) {
        this.attFloat2 = attFloat2;
    }
    public Double getAttDouble2() {
        return attDouble2;
    }
    public void setAttDouble2(Double attDouble2) {
        this.attDouble2 = attDouble2;
    }
    public Character getAttCharacter2() {
        return attCharacter2;
    }
    public void setAttCharacter2(Character attCharacter2) {
        this.attCharacter2 = attCharacter2;
    }
    public BigDecimal getCost() {
        return cost;
    }
    public void setCost(BigDecimal cost) {
        this.cost = cost;
    }
    public BigDecimal getCost2() {
        return cost2;
    }
    public void setCost2(BigDecimal cost2) {
        this.cost2 = cost2;
    }
    
//setStoreList,getStoreList,setEditionList,getEditionListに
//対策コードを追加したので以下は使用しない。
//    /*
//     * getter/setterに加えて、このメソッドを追加する理由
//     * OneToManyの関連に関連先を追加するためには、
//     * book.getEditionList().add(newEdition)として、persist(book)としても追加されない。
//     * newEdition.setBook(book)としてから、persist(book)しなければならない。
//     * ただ、シーケンスとしてbook側を変更するだけにしたい場合もあるので、
//     * 以下のようなaddEdition(newEdition)を実装した。
//     * ただし、双方向維持のための常套コードのように
//     * edition.setBook(book)からbook.addEdition(edition)とすると、
//     * 復元時に"復元中にコレクションが変更された"という例外が発生するので、
//     * edition.setBook(book)からbook.addEdition(edition)は使用しないようにした。
//     */
//    public void addEdition(Edition edition){
////        if(edition!=null && !this.editionList.contains(edition)){
////            edition.setBook(this);
////            this.editionList.add(edition);
////        }
//        //Hibernate ORM 5.2 User Guide2.7.2 Bidirectional @OneToMany 例を参考
//        this.editionList.add(edition);
//        edition.setBook(this);
//    }
//
//    /**
//     * Hibernate ORM 5.2 User Guide2.7.2 Bidirectionaln@OneToMany 例を参考
//     * @param edition
//     */
//    public void removeEdition(Edition edition){
//        this.editionList.remove(edition);
//        edition.setBook(null);
//    }
//
//    /**
//     * ManyToManyのowning sideなので自分のリストのみ更新する。
//     * Store(mappedBy側)との間でaddBookから折り返すと復元時に
//     * "復元中にコレクションが変更された"例外が発生すると思われる
//     */
//    public void addStore(Store store){
//        //Hibernate ORM 5.2 User Guide2.7.2 Bidirectionaln@ManyToManyでは
//        //Store(mappedBy側)にHelperメソッドは無い。
////        if (store != null && !this.storeList.contains(store)){
////            this.storeList.add(store);
////        }
//        this.storeList.add(store);
//        store.getBookList().add(this);
//    }
//    
//    /**
//     * 双方向関連を整合させるためのアプリケーションコード
//     */
//    public void removeStore(Store store){
//        this.storeList.remove(store);
//        store.getBookList().remove(this);
//    }
}


/*
 * ドメインモデル例
 */
package jp.co.nextdesign.domain;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import jp.co.nextdesign.domain.ddb.DdBaseEntity;

/**
 * 版
 */
@Entity
public class Edition extends DdBaseEntity {
    private static final long serialVersionUID = 1L;

    /** 版番号 */
    private Integer editionNumber;
    
    /** 版名 */
    private String name;
    
    /** 書籍 */
    @ManyToOne
    //@JoinColumn(name="book_id") //省略可
    private Book book;

    /** 書籍 */
    @ManyToOne
    //@JoinColumn(name="book_id") //省略可
    private Book book2;

    public Edition(){
        super();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getEditionNumber() {
        return editionNumber;
    }

    public void setEditionNumber(Integer editionNumber) {
        this.editionNumber = editionNumber;
    }

    public Book getBook() {
        return book;
    }

    //Book#addEdition,removeEditionから使用する
    public void setBook(Book book) {
        this.book = book;
    }    

    public Book getBook2() {
        return book2;
    }

    public void setBook2(Book book2) {
        this.book2 = book2;
    }

    @Override
    public String getDDBEntityTitle(){
        return this.name;
    }

    /** debug */
    public String getDebugInfo(){
        String info = "<" + this.getClass().getSimpleName() + ">";
        info += "\neditionNumber=" + this.getEditionNumber();
        info += "\n</" + this.getClass().getSimpleName() + ">";
        return info;
    }
}


/*
 * ドメインモデル例
 */
package jp.co.nextdesign.domain;
public enum EnumLanguage {
    JA("日本語"),
    EN("英語");

    private String fullName;
    private EnumLanguage(String fullName){
        this.fullName = fullName;
    }

    @Override
    public String toString(){
        return this.fullName;
    }
}


/*
 * ドメインモデル例
 */
package jp.co.nextdesign.domain;
import java.util.Date;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import jp.co.nextdesign.domain.ddb.DdBaseEntity;

/**
 * ISBN
 */
@Entity
public class Isbn extends DdBaseEntity {
    private static final long serialVersionUID = 1L;
    
    /** 書籍 one-to-one non-owning/child Hibernate ORM 5.2 User Guide2.7と異なる */
    @OneToOne(mappedBy="isbn") //相手側(owning)の属性
    private Book book;

    /** 書籍 one-to-one non-owning/child Hibernate ORM 5.2 User Guide2.7と異なる */
    @OneToOne(mappedBy="isbn2") //相手側(owning)の属性
    private Book book2;

    /** グループ記号 */
    private String groupCode;
    
    /** 出版社記号 */
    private Integer publisherCode;
    
    /** 書名記号 */
    private String itemCode;
    
    /** チェックディジット */
    private String checkDigit;
    
    /** 決定日 */
    private Date determinatedAt;
    
    /** 旧ISBN */
    private Boolean isOldIsbn;
    
    /** コンストラクタ */
    public Isbn(){
        super();
    }

    public Book getBook() {
        return book;
    }

    public void setBook(Book book) {
        this.book = book;
    }
    
    public Book getBook2() {
        return book2;
    }

    public void setBook2(Book book2) {
        this.book2 = book2;
    }

    public String getGroupCode() {
        return groupCode;
    }

    public void setGroupCode(String groupCode) {
        this.groupCode = groupCode;
    }

    public Integer getPublisherCode() {
        return publisherCode;
    }

    public void setPublisherCode(Integer publisherCode) {
        this.publisherCode = publisherCode;
    }

    public String getItemCode() {
        return itemCode;
    }

    public void setItemCode(String itemCode) {
        this.itemCode = itemCode;
    }

    public String getCheckDigit() {
        return checkDigit;
    }

    public void setCheckDigit(String checkDigit) {
        this.checkDigit = checkDigit;
    }
    
    public Date getDeterminatedAt() {
        return determinatedAt;
    }

    public void setDeterminatedAt(Date determinatedAt) {
        this.determinatedAt = determinatedAt;
    }

    public Boolean getIsOldIsbn() {
        return isOldIsbn;
    }

    public void setIsOldIsbn(Boolean isOldIsbn) {
        this.isOldIsbn = isOldIsbn;
    }

    @Override
    public String getDDBEntityTitle(){
        return "ISBN" + this.getGroupCode() + "-" 
        + this.getPublisherCode() + "-" + this.getItemCode();
    }
    
    /** debug */
    public String getDebugInfo(){
        String info = "<" + this.getClass().getSimpleName() + ">";
        info += "\ngroupCode=" + this.getGroupCode();
        info += "\npublisherCode=" + this.getPublisherCode();
        info += "\nitemNumber=" + this.getItemCode();
        info += "\n</" + this.getClass().getSimpleName() + ">";
        return info;
    }
}


/*
 * ドメインモデル例
 */
package jp.co.nextdesign.domain.store;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ManyToMany;
import jp.co.nextdesign.domain.Book;
import jp.co.nextdesign.domain.ddb.DdBaseEntity;

/**
 * 書店
 */
@Entity
public class Store extends DdBaseEntity {
    private static final long serialVersionUID = 1L;
    
    /** 書店名 */
    private String name;
    
    /** 書籍リスト */
    @ManyToMany(mappedBy="storeList", fetch=FetchType.EAGER)
    private List<Book> bookList;

//    /** 書籍リスト Book側のコメントを参照 */
//    @ManyToMany(mappedBy="storeList2")
//    private List<Book> bookList2;

    /** コンストラクタ */
    public Store(){
        super();
        this.name = "";
        this.bookList = new ArrayList<Book>();
//        this.bookList2 = new ArrayList<Book>();
    }

    /** DDBのviewが使用する */
    @Override
    public String getDDBEntityTitle(){
        return this.name;
    }
    
    //ManyToManyで双方向関連を維持するためのaddStore,removeStoreを含む。
    //owning側ではなくmappedBy側から使用するが、両側に実装する。
    public List<Book> getBookList() {
        return bookList;
    }
    public void setBookList(List<Book> bookList) {
        this.bookList = bookList;
    }
    public void addBook(Book book){
        if (book != null && !this.bookList.contains(book)){
            this.bookList.add(book);
            book.addStore(this);
        }
    }
    public void removeBook(Book book){
        if (book != null && this.bookList.contains(book)){
            this.bookList.remove(book);
            book.removeStore(this);
        }
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    /** debug */
    public String getDebugInfo(){
        String info = "<" + this.getClass().getSimpleName() + ">";
        info += "\nname=" + this.getName();
        info += "\n</" + this.getClass().getSimpleName() + ">";
        return info;
    }
}

ダウンロードページへ

[ 戻る]


Copyright 2001-2018, All Rights Reserved