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

更新日

ドメイン駆動設計を導入し、結果を出すためには工夫が必要です。

最初はドメイン内の小さなコアモデルから始めて、リファクタリングを繰り返しながら、ドメイン駆動設計スキルとドメインモデルの両方を、段階的に洗練していくことが良策です。

そのためには、動作可能で検証可能なモデルをベースに、短いサイクルで繰り返せるイテレーションの仕組みが必要です。

本記事では、ドメイン駆動設計の基本的な概念や考え方を共有し、次に、実践的な開発プロセスとして「Javaドメインモデルファースト開発」と、その支援ツールを紹介します。 また、紹介した支援ツールはフリーソフトで公開します。


 

ドメイン駆動設計

ドメイン駆動設計 (英: Domain-driven design, DDD) は、 Eric Evans氏が、著書「エリック・エヴァンスのドメイン駆動設計」(以降 DDD本) で提唱するソフトウェア設計手法です。

書籍情報: 翔泳社 (ISBN 9784798121963)

 

基本的な考え方

ドメイン駆動設計は、

・アプリケーションの本質はドメイン (後述) であること。

・そのドメインモデルを中心に設計・構築すること。

を原則とする設計手法です。


手法的には、

・モデル駆動開発

・オブジェクト指向技術

・アジャイル開発

といった、すでに実績のある手法と、そこで培われたノウハウを活かします。


そして、そこにいくつかの前提を加えます。

・「ドメインモデル貧血症」と言われるような、責務が欠落したクラスは作りません。

・「分析モデル」や「設計モデル」のように、モデルの役割や位置づけを分離させません。1つのモデルを作成し、それを使い続けます。

・コミュニケーションや成果物には、ユビキタス言語の使用を徹底します。

・ユビキタス言語、モデル、実装の整合性を保ち、相互に追跡可能にします。


(すでに登場しましたが) ドメインとは、システム化対象領域を指します。例えば、在庫管理業務などです。

ドメインモデルとは、ドメインに存在する概念などを抽象化 (オブジェクトモデリング) したものです。

ドメインモデルは、多くの業務アプリケーションで採用されているレイヤ構成 (ビュー層/ドメイン層/永続化層) のドメイン層に存在します。

ただし、「ドメイン貧血症」と言われるような、アンチパターンの構成をイメージすると、出だしから間違ってしまうことになります。

そのようなアンチパターンのドメイン層にあるのは、ビュー層と永続化層の間でデータのバケツリレーをするだけのクラスであって、ドメインモデルとは言えません。

ドメインモデルは、アプリケーションの本質的な責務を持った概念、オブジェクトの集まりを意味します。


ドメイン駆動設計のバイブルである DDD本には、かなり多くのことが書かれています。

そのため、ドメイン駆動設計を一気に理解したり、簡潔にまとめることは容易ではありません。

また、読者によっても、視点や重要と位置付けるポイントが異なることもあると思われます。

ここではドメイン駆動設計を、方法論として見た場合と、パターン集として見た場合の2つの視点で考えてみます。


まず、方法論 (手法の議論) として見た場合、ドメイン駆動設計は "何か新しい特別な技法" を示しているわけではありません。

その代わりに、すでに多くの知見やノウハウが蓄積された既知の手法を使用します。

それは主に、モデル駆動開発、オブジェクト指向モデリング/プログラミング、アジャイル手法です。


ドメイン駆動設計において、オブジェクト指向は必須ではないようですが、実績やモデル駆動との相性を考えると、事実上の最適なパラダイムと言えます。

OMGのモデル駆動開発 (Model-Driven Development, MDD) は、分析・設計・実装・テストといった開発の成果をモデルとして作成し、 モデル変換を繰り返すことでアプリケーションを開発する手法です。

DDDのモデル駆動設計 (Model-Driven Design) は、分析モデルと設計モデルといった分け方をしないで、両方の目的で使える唯一のモデルを考えます。

分析モデルと設計モデルという考えの場合、担当者や工程で分離されたり、特に実装段階では、それぞれのモデルが乖離することがあります。

モデル駆動設計は、このような分断をしないで、1つのモデルだけを開発して、そのモデルと一致した実装を行います。

モデルは、ドメインに存在する概念やドメインエキスパートが使う言葉などを、ソフトウェア設計の土台にできるように整理したものです。


つぎに、設計パターン集や設計構築のガイドライン集という視点で見ると、 一覧形式になってはいませんが、 DDD本には多くの有用なパターンやガイドラインが書かれています。

本記事でも、基本概念、キーワードとして幾つか名前を挙げています。全容はDDD本を参照してください。


ドメイン駆動設計のポイントをまとめてみます。

アプリケーションの本質はドメインです。

ドメインを抽象化したドメインモデルを第一の土台にして、アプリケーションを構築します。

ドメイン知識からユビキタス言語を導き出し、チーム内のコミュニケーションに使用します。

ドメインモデル、実装コード、ユビキタス言語 の3つを常に一致させて、アプリケーションをイテレーティブに構築します。

ドメイン駆動設計は、アプリケーションの設計・構築活動に有用なモデリングパターンやガイドラインを示します。


ドメイン駆動設計に関する情報源としては、DDD本以外にも多くの記事や書籍かあります。

その中から、「Domain-Driven Design Quickly」を紹介しておきます。

この文献は、Abel Avram氏とFloyd Marinescu氏の共著で、徳武 聡氏によって翻訳されたものです。

A4サイズで90ページ程で、要約本としてよくまとまっていて、日本語も分かり易く、ドメイン駆動設計のエッセンスだと思います。


先にも書きましたが、DDD本には多くの事が書かれています。

実際、最初から多くのプラクティスやパターンを取り入れて、うまく実践できたという事例は稀でしょう。

ドメイン駆動設計はイテレーティブな開発を基本としています。

つまり、パターンやプラクティスを段階的に取り入れていって、ドメインモデル、アプリケーションを高度化し深化させていきます。

従って、ドメイン駆動設計スキルも段階的に向上させていければよいわけです。

ウォータフォール的な発想や計画では、ドメイン駆動設計は成功しないでしょう。

以降では、DDD本の内容を網羅することよりも、まず始めるために、押さえておくべき基本を知識共有したいと思います。

[目次に戻る]
 

実践的モデラ

開発チームは、ユーザ、ドメインエキスパート (業務の専門知識をもつ人) 、ソフトウェア技術者で構成されます。

チーム内では、(後述の) ユビキタス言語を使ってコミュニケーションを行います。

実践的モデラ (パターン) とは、 分析、モデリング、設計、プログラミングというように、過度に役割を分離しないで、 チームメンバー全員が、モデラであり、プログラマであることです。


例えば、分析モデルという名前は、他の手法でもよく使われますが、これは、主に業務分析工程で作成される、ビジネス視点のモデルです。

設計モデルは、ソフトウェアの土台となる、ソフトウェア技術視点のモデルです。

実装モデルは、プログラム・コードです。

これらを、担当者や役割を (過度に) 分けずに、全員が一通り実践できるチームが必要です。


もしも現実問題として、実践的モデラが実現困難な場合でも、全員がモデルと実装コードを理解し、関心と責任を持つことが求められます。

モデリングとプログラミングを分離すると、モデルと実装の不一致など、ドメイン駆動設計はうまくいきません。

例えば、モデリング時に何か気づいてモデルを変更した場合には、その変更は、ユビキタス言語と実装コードに、正確に反映されなければなりません。

また、実装時に気づいた問題は、ドメインモデルとユビキタス言語に反映されなければなりません。

同様に、コミュニケーション中に気づく問題もあるでしょう。それも、ドメインモデルと実装コードに反映されなければなりません。

ユビキタス言語に変更 (用語の意味の変更など) があった場合には、会話の中に正確に反映されなければなりません。

つまり、ユビキタス言語、モデル、実装の一貫性を保つためには、全員が (少なくとも主要なメンバーは) 実践的モデラであることが求められます。

[目次に戻る]
 

ユビキタス言語

※DDD本から引用:ここから

「モデルを言語の骨格として使用すること。 チーム内のすべてのコミュニケーションとコードにおいて、その言語を厳格に用いることを、チームに約束させること。 図やドキュメント、そして何より会話の中では同一の言語を使用すること。 言語を使う上で問題があれば、代わりの表現を用いて実験することで、問題を取り除くこと。 そうした表現は代りとなるモデルを反映している。 そこで、新しいモデルに合わせてコードをリファクタリングし、クラス、メソッド、モジュールの名前を変更すること。 会話の中で用語が混同されていたら、普通の単語の意味について認識を合わせるのと同じやり方で解決すること。 ユビキタス言語における変更は、モデルに対する変更であると認識すること。」

※DDD本から引用:ここまで


ソフトウェア技術者はクラスやメソッドなどに関心があり、それらを実世界の概念と結び付けて思考し会話します。

一方、ユーザやドメインエキスパートには、クラスなどの知識はありません。

もし「クラス」という単語を使っていたとしても、それば、ソフトウェア技術者が使うクラスとは別の何かである可能性があります。

ドメインモデルを作成するときは、チーム内でのコミュニケーションがとても重要です。

ドメイン駆動設計では、このコミュニケーションを正確にするために、共通言語として「ユビキタス言語」を明確に位置づけます。

そして、必ずユビキタス言語を使ってコミュニケーションするようにします。


ユビキタス言語があれば、例えば「見積書」という名前は、会話の中でも、モデルの中でも、実装コードの中でも、そのまま「見積書」と現され、同じ概念を指します。

アプリケーションユーザも、ドメインエキスパートも、ソフトウェア技術者も、頭の中で読み替えたりしないで、そのまま使えます。

ただし日本では、「見積書」はローマ字表記になるかもしれません。

英語表記になる場合は、読み替えが必要になるので、その点は検討が必要かもしれません。


ユビキタス言語の語彙は、モデルや実装コードのクラス名、メソッド名などと双方向に追跡可能 (紐付けられる) にします。

[目次に戻る]
 

ドメインモデル

ドメインモデルとは、あるドメイン (アプリケーション領域、業務領域) に存在する概念を抽象化したものです。

ドメインモデルは、ユビキタス言語、UML図などのモデリング言語や記法で表現されたモデル、ソフトウェアで表現されたモデルで定義されます。

本記事で使っています、Javaドメインモデルは、ソフトウェアで表現されたモデルを指します。

ソフトウェアで表現されたドメインモデルを構成するオブジェクトのパターンには、エンティティ、値オブジェクト、サービスがあります。

[目次に戻る]
 

エンティティ

ドメインモデル内のオブジェクトは主に、エンティティ、値オブジェクト、サービスの3つに分類されます。

エンティティは、連続性と識別性 (同一性と同値は区別される) を持ちます。

少し乱暴ですが、データベースの論理設計で抽出された論理テーブルの1つ1つは、(かなりの確率で) 妥当なエンティティと言えます。

実装の観点では、エンティティは、テーブル名や列名を、そのクラス名や属性名として持つクラスです。

ただし、テーブルにはありませんが、エンティティはドメインにおける重要で本質的な責務をメソッドなどとして持ちます。

これは、重要な違いです。

ドメイン駆動設計はオブジェクト指向モデリングを必須とはしませんが、多くのチームが、オブジェクト指向モデリングを採用するでしょう。

そこで、オブジェクトを見つけるときのヒントとして、少々、古典になりますが、「オブジェクト指向システム分析」シュレイアー、メラー著 にクラスの候補として以下が挙げられています。

  • 有形物
    • 人、商品、伝票。
  • 役割
    • 人や構成により演じられる役割。医師、患者、顧客、従業員。医師は患者にもなる。
  • 出来事
    • 飛行、事故、故障、サービス要求。
  • 相互作用
    • 買い入れ、結婚。
  • 仕様
    • 製品などの仕様。

上にも述べましたが、DOA (データ中心アプローチ) になれている方の場合、1つのテーブルとして抽出した概念やモノは、多くの場合、エンティティとして適切か、有力な候補になるはずです。

[目次に戻る]
 

値オブジェクト

識別性を持たない、変更不可の不変 (immutable) オブジェクトです。

多くの場合、エンティティオブジェクトの状態を記述する属性として振る舞います。

例として、図形処理アプリケーションの座標点クラス (Point) があります。

多くの場合、エンティティの属性の状態を現すクラスは値オブジェクトとなります。

値オブジェクトには一意性は必要なく、同値性が重要です。

エンティティの属性の状態を示すためには、使用するプログラミング言語のプリミティブ型で十分なケースも多くあります。

しかし、Pointクラスのような値オブジェクトをドメインモデルに追加することで、ドメインモデルがより洗練されます。

ただし、過度に追加するとクラスの爆発と言われるような状況に陥ることもあります。

[目次に戻る]
 

サービス

ドメインには、エンティティや値オブジェクトとして扱うには不自然なものが存在します。

GRASPパターンの純粋人工物 (Pure Fabrication)です。

ドメインには実在しないオブジェクトであり、サービスという形でユビキタス言語に組み込みます。

サービスは基本的に状態を持ちません。

エンティティや値オブジェクトではなく、アクションや操作といった概念として存在します。

[目次に戻る]
 

モジュール

Javaのパッケージと同義です。モジュール名もユビキタス言語に含まれる名前です。

モジュール名、モジュール構成もリファクタリングの対象です。

モジュール間は低結合、モジュール内は高凝集にします。

[目次に戻る]
 

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

オブジェクト指向技術やモデル駆動設計を実践してきた技術者にとって、DDD本は、ツボを得た考察やモデリングパターンを示してくれます。

また、次に向かうべき方向を示してくれる心強い指南書です。

一方、「オブジェクト思考」ではなく「手続き思考」の技術者にとっては、DDD本を読んで理解していくのは、相当の時間を要するかもしれません。

念のためですが、オブジェクト指向言語を使っていても、オブジェクト思考になっているとは限りません。

ドメイン駆動設計を始めるためには、最小限、DDD本の「利口なUI」パターンのメリットとデメリットを理解しておく必要があると思います。


例えば、Struts系のフレームワークを使ったWebアプリケーションが、下図のような構造になっているならば、オブジェクト思考ではありません。

ドメイン駆動設計的にはアンチパターンですが、実際の開発現場では多く使われているのが現実のようです。

また、よく似たパターンにトランザクションスクリプトと呼ばれるものがあります。

トランザクションスクリプトは、マーチン・ファウラー氏の著書「エンタープライズアプリケーションアーキテクチャパターン」に示されています。

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点示されています)

[目次に戻る]
 

他の手法との関係

ドメイン駆動設計では、新しいモデリング技術や特別な設計プロセスは提唱していません。

ドメイン駆動設計は「アプリケーション構築において最も重要なものはドメインモデルである」とし、ドメインモデルを中心に置く設計手法です。

ドメインモデリングには、モデル駆動設計 (MDD) や、オブジェクト指向 (OO) の世界で培われてきたノウハウを最大限活用します。

ドメイン駆動設計は、モデル駆動設計やオブジェクト指向技術と協調します。

モデル中心の考え方はモデル駆動設計から取り入れ、 そのモデルを表現する手段としてオブジェクト指向のパラダイムを使用します。

(オブジェクト指向は必須ではありませんが、事実上使用されます)

モデル駆動設計やオブジェクト指向はモデル化する対象を特に限定しませんが、ドメイン駆動設計はアプリケーションのドメインを中心に置きます。

例えば、MVCモデルのビュー層や永続化層は、ドメイン駆動設計において、主要な関心事ではありません。

ドメイン駆動設計では、開発プロセスとしてアジャイル開発が基本です。

※ 本記事では、アジャイル開発とイテレーティブ開発、反復型開発を区別しないで混用します。

[目次に戻る]
 

モデリングパラダイム

ドメイン駆動設計、モデル駆動設計では、モデリングパラダイムを限定しませんが、 実際に主流となっているのは、オブジェクト指向パラダイムです。 DDD本以外にも、ドメインモデル作成の最初の取っ掛かりや、その後のリファクタリングに役立つモデリング手法があります。 参考になりそうな手法を次に挙げてみます。

[目次に戻る]
 

アナリシスパターン

マーチン・ファウラー氏の著書「アナリシスパターン」には、再利用可能なオブジェクトモデルがパターンとして示されています。 これらのオブジェクトモデルは、著書の中の特定の業務 (ドメイン) だけでなく、他のドメインにも適用できるものです。 自分たちのアプリケーションのドメインモデルとして、そのまま適用できるケースは少ないかもしれませんが、 ドメインモデルを作成するときの貴重な手本になると思われます。 アナリシスパターンで示されているパターンは、よく洗練されたものであり、深いモデルです。 時として、深すぎて難しく感じるものも少なくないかもしれません。 しかし、深さのレベルを少し落として取り入れたり、ドメインモデリングを始めるときのヒントにするだけでも、とても有益なパターン集だと思います。

[目次に戻る]
 

デザインパターン

GoFと呼ばれる4人による共著「オブジェクト指向における再利用のためのデザインパターン」では、 オブジェクト指向設計、実装に関する23個のパターンを示しています。 各パターンは主に次の4つに分類されます。

(1) 生成に関するパターン

(2) 構造に関するパターン

(3) 振る舞いに関するパターン

(4) マルチスレッドプログラミングに関するパターン

実装の観点からの設計パターンです。ドメイン駆動設計のドメインモデルを作成する際や、リファクタリングする際の有用な指針となるでしょう。そのまま適用できるケースも少なくないと思われます。 GoFとは、エーリヒ・ガンマ、リチャード・ヘルム、ラルフ・ジョンソン、ジョン・ブリシディースの4人です。

[目次に戻る]
 

CRC (Class Responsibility Collaborator)

ドメイン駆動設計とは直接関係しませんが、オブジェクト指向分析ツールにCRCカードがあります。 CRCカードは、オブジェクトの発見ツールとして使用されます。また、オブジェクト指向の初学者のための演習用としても良く使用されます。 CRCは同じ大きさの小さなカードを使用します。 1枚のカードにオブジェクトの候補を1つ記入し、クラス名、責務、協調者を記入します。 実際にオブジェクトの候補を挙げながら、その責務や、(責務を果たすために) 相互作用すべき協調者 (他のオブジェクト) を実際に書いてみることが、 オブジェクトの発見や、発見するためのアプローチを理解するのに効果があるからです。 CRCでは分析チームのメンバーや演習の参加者同士が机上と口頭で相互作用してみて、候補に挙げたオブジェクトに正しい責務が割り当てられ、 期待する振る舞いを実現できるか等を検証します。 ドメイン駆動設計においても、もし、ドメインオブジェクトの見つけ方で悩まれているならば、CRCカードを使ってみる価値があると思います。

[目次に戻る]
 

Naked Objects パターン

ここで、ドメイン駆動設計に通じるコンセプトを持つ Naked Objects パターンを紹介します。 Naked Objects パターンでは、ドメイン駆動設計と同様にドメインモデルに重点を置きます。 アプリケーションを開発する時には、ドメインオブジェクトだけを作成します。 ユーザインタフェース層やデータアクセス層は、ドメインオブジェクトから自動で作成します。 十分に洗練され深化したドメインモデルがあれば、 ドメインモデルの各インタフェース (サービスやクラスのメソッド、プロパティ) をビューとしてユーザに公開 (画面など) するだけで、 アプリケーションとして機能する、という考えです。 大きなアプリケーションでの事例もあるようです。 本記事で紹介する開発支援ツール DDBuilderは、Naked Objects パターンにインスパイア (触発) されたものです。

[目次に戻る]
 

実践するときのポイント

ドメイン駆動設計では反復型開発 (イテレーティブ開発) が基本です。

リファクタリング (洗練) と検証を繰り返し、段階的にモデルの完成度を上げていく手法です。

開発プロセスの中で繰り返す単位をイテレーションと呼び、1回のイテレーションの中で、分析から設計、実装、検証までを完結させます。

ドメインモデルと実装を常に結びつけて繰り返すことが重要です。


[反復型開発の流れ]

反復型開発の流れ図

 

ドメインモデルを常に動かす

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

ソフトウェアで表現されたドメインモデル (例: Javaで実装されたドメインモデル) も、実際に動かすためには単体テストコードやドライバのようなものが必要です。 ここでは「動くソフトウェアモデル」とは、「ソフトウェアで表現されたドメインモデル」のサービスやメソッドを実行できる画面を持った「Webアプリケーション」を指すこととします。

動くソフトウェアモデル (動くアプリケーション) は次のような観点で必要です。

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

もしも、動くソフトウェアモデルを作らずに、机上のUML図や単体テストレベルのテストコードだけでドメインモデルを検証しようとすると、 検証の精度や手間、メンバー間の齟齬の発生などが課題となるでしょう。

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

 

質の高いイテレーション

イテレーションは反復型開発のコアプロセスです。 動くソフトウェアモデルで検証すればイテレーションの品質・精度が上がります。 イテレーションを軽快に繰り返すことができれば、チーム (ユーザ、ドメインエキスパート、技術者など) の関心事をドメインモデルに集中できます。

ただ現実には、イテレーションを何度も繰り返すなかで、動くソフトウェアモデルを ”いつでも動く状態” に維持することは、簡単ではありません。 ドメインモデルを動かすために、想定以上に時間を要するケースが多いからです。 例えば、ドメインモデルをWebアプリケーションで動かそうとする場合、イテレーションの度に、即ちドメインモデルをリファクタリングする度に、ユーザインタフェース層やインフラストラクチャ層などの更新も必要になるからです。 イテレーションの本来の目的はドメインモデルのリファクタリング (洗練) です。しかし、動くソフトウェアモデルの維持に手間がかかって、簡単には繰り返せなくなることがあります。 1回1回のイテレーションが重くて手間がかかるようでは、反復型開発の効果は得られません。 この問題を解決するためには、開発プロセスの一部を自動化するなど、何らかの対策が必要です。

 

アプリケーション自動生成の効果

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

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

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

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

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

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

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

ドメインモデルのサービスやメソッドを検証するためのインタフェース画面やテストコードの実装作業、 ドメインモデルの永続化の実装作業などのリファクタリング (洗練) に伴う作業です。

[目次に戻る]
 

Javaドメインモデルファースト開発

「Javaドメインモデルファースト開発」は、ドメイン駆動設計のドメインモデルをコードファーストでイテレーティブに開発するスタイルです。

基本的に、モデル図よりも、コードで表現したモデル (Javaコード) を先行させる開発スタイルです。

もちろん、モデル図で考えた方が分かり易い部分は、モデルファーストで行うべきです。

コードファーストのメリットは、モデル図はそのまま動かすことはできませんが、コードファーストであれば、(ツール等の支援、例えば次項の DDBuilder があれば) いつでも動かして、検証できることです。

Javaコードからモデル図を作成するツールを利用すれば、常に、実装とモデルが一致し、動作可能で検証可能なドメインモデルを維持することができます。

 

開発支援ツール DDBuilder

DDBuilderは、JavaドメインモデルからWebアプリケーションを自動生成するツールです。

DDBuilderを活用すると、ドメインモデリングを Java コードファーストでイテレーティブに実践できます。 この記事でのコードファーストとは、モデル図を必要としないという意味ではありません。 モデル図で検証する方が良いと思われる部分は、コードよりも先にモデル図を作成するべきです。 ただし、そのモデル図はスケッチの方が良いかもしれません。 正確なモデル図は、UMLツール等でコードから生成するようにしないと、モデル図と実装を一致させることは困難です。なによりツール無しでは工数がかかるからです。 ドメインモデルからアプリケーションを自動生成するソフトウェアツールがあれば、ドメインモデルの リファクタリング → 動くソフトウェアモデルで検証 → リファクタリング → … という繰り返しがスムーズに実践できます。

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


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

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

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


「ドメインモデルファースト開発」の流れ図

【役割】

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

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

イテレーションの度に行っていたユーザインタフェース層やインフラストラクチャ層の実装作業がドメイン中心になり、開発チーム (ユーザ、ドメインエキスパート、技術者など) はドメインモデルの分析・設計・実装・評価に集中できるようになります。 単純な実装ミスも減少します。

 

成果物はソフトウェアで表現されたドメインモデル

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

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

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

Webアプリケーションはドメインモデルを評価するための入れ物にすぎません。

従って、DDBuilderは、Webアプリケーション自動生成というよりも、ドメインモデルのインキュベータ (孵化器) と考えた方がより適切です。

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

 

ドメインモデルをJavaコードファーストでリファクタリング

イテレーションの流れ図

[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の基本操作

関連する知識

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

  • アナリシスパターンは、実際のドメインモデルの具体例、パターンです。他のドメインでも参考になるようなドメインモデルの構造、パターンを示しています。一方、ドメイン駆動設計は、モデリングについてのパターンや指針を示しています。
  • デザインパターンは、オブジェクト指向設計パターン、実装パターンです。          
  • ドメイン駆動設計は、モデリングのパターンです。
[目次に戻る]
 

ドメイン駆動設計の入門や学習に活用

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

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

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

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

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

書籍など

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

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

[目次に戻る]
 

ユーザガイド

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

[目次に戻る]
 

ダウンロード

ダウンロードページへ...

DDBuilderは、ネクストデザイン有限会社が公開する無料・無保証のソフトウェアツールです。

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

お問合せ

[目次に戻る]
 

動作確認済み環境

  • 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
[目次に戻る]
 

インストールとアンインストール (zip解凍/削除)

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

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

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

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

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

「DDBuilder_Guide_Ver2.pdf」

ダウンロードページへ

[目次に戻る]
 

謝辞

DDBuilderは次のソフトウェアを利用しています。
[目次に戻る]

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

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

弊社の都合で返信が遅くなる場合がありますが、ご了承ください。

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

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

[目次に戻る]

参考

例1「Javaドメインモデルファースト開発」の流れ

この例では、ドメイン駆動設計を「Javaドメインモデルファースト開発」で行う場合のイテレーションの流れを示します。

この例のドメインは、簡単な在庫管理業務とします。

倉庫、製品、在庫、在庫管理サービスといったドメインモデルを作り上げていく流れを示します。

DDBuilderとEclipseの関係

下図の内容を画面キャプチャを使って説明します。

「Javaドメインモデルファースト開発」でのDDBuilderとEclipseの役割図

DDBuilder の起動

(1) 起動するためには、DDBuilder 圧縮ファイル (jp-co-nextdesign-ddbuilder.zip) をダウンロードします。 [ダウンロードページへ]

(2) DDBuilder 圧縮ファイルを適当な場所に解凍します。

(3) 解凍後フォルダ (jp-co-nextdesign-ddbuilder) の直下の startDdbuilder.bat を起動します。

(4) 起動すると下図の画面が表示されます。

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

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

基本情報の設定

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

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

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

下図は入力例です。

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

初回の生成

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

この時点ではドメインモデルはひとつも定義されていませんので、生成されたWebアプリケーションは最小限の画面 (トップ画面など) を含むだけです。

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

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

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

Eclipse画面例


※ DDBuilder は起動したままでも構いません。一度終了してから、必要なときに再起動しても構いません。

※ 再起動した場合も、前回入力されたDDBuilder画面の基本情報は維持されています。

Webアプリケーションの実行確認

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

確認のために、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を追加します。サブパッケージを追加することもできます。

EclipseでProduct.javaを作成するときの例

ドメインモデル (Productクラス) を Webアプリケーションに反映

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

しかしまだ、Productクラスのインスタンスを登録したり、表示したりするためのビュー層 (ユーザインタフェース層) や永続化層 (インフラストラクチャ層) はありません。

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

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

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

DDBuilder 操作画面

Eclipse プロジェクトをリフレッシュ

作成/更新ボタンを押下し、完了ダイアログが表示されたら、Webアプリケーションの中に必要なjavaファイルが追加されています。

しかし、Eclipseは、追加されたファイルを感知していません。

そのために必ず、Eclipse側でプロジェクトをリフレッシュしてください。(忘れがちです)

再度 Webアプリケーションを動かしてテスト

リフレッシュしたら、再度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)をご覧ください。

[ ダウンロードページへ ]

[ 戻る ]



例2 DDBuilderが生成するアプリケーションのアーキテクチャ - レイヤ化アーキテクチャ

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

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

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


DDBuilderが生成するアプリケーションのレイヤ化アーキテクチャ図


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の知識が必要になります。

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


[ ダウンロードページへ ]

[目次に戻る]



例3 DDBuilderがサポートする関連タイプと属性型のサンプル

このドメインモデルは、DDBuilderがサポートする関連の種類、インスタンス属性型の種類を確認するためのサンプルです。

そのため、ドメイン駆動設計のサンプルらしいメソッドは無いことに注意してください。

[コードをダウンロード]


UMLクラス図

関連の例


DDBuilder 関連タイプと属性型のコードサンプル


/*
 * 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;
    }
}

[ ダウンロードページへ ]

[ 戻る ]

例4 クラス図

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

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

また当社では、

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

[目次に戻る]


例5 オブジェクト指向やドメイン駆動設計を導入する時の傾向

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

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


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


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

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


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

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


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

[ ダウンロードページへ ]

[ 戻る]


Copyright 2001-2018, All Rights Reserved