Upload
others
View
0
Download
0
Embed Size (px)
Citation preview
ORACLE.COM/JAVAMAGAZINE /////////////////////////////////// MAY/JUNE 2018
40
//design patterns /
ドメイン駆動デザインは、Eric Evans氏の著書で説明されているように、実際のビジネス・ドメインをできる限り正確に
表現するソフトウェア・モデルの構築を目指すものです。そこで特に重視されているのは、ドメインのエキスパートと
のコミュニケーションの必要性、共通したユビキタスなドメイン言語の共有、基盤となるドメイン・モデルの理解向上、そ
して徐々にそのモデルをリファクタリングすることです。
ドメイン駆動デザイン(DDD)では、境界づけられたコンテキスト、集約、エンティティといったいくつかの概念が定
義されています。こういった概念は、Java EEや近くリリースされる予定のJakarta EEで実装できるのでしょうか。本記事では、
これらの概念と、適切なドメイン・モデルを構築する際に最新のJava EEプログラミング・モデルがどう役立つかについて
説明します。まずは、いくつかの基本的な定義から始めます。
境界づけられたコンテキスト境界づけられたコンテキストには、ドメインのどこか一部の意味と責務が含まれます。境界づけられたコンテキスト内の具
体的なドメイン・エンティティとしては、たとえば顧客が考えられます。境界および責務に加えて、想定される、境界づけ
られたコンテキストの重なりが、システムのコンテキスト・マップに定義されます。
マイクロサービス・モデルでは、境界づけられたコンテキストが1つのデプロイ可能アプリケーションとなるのが一般
的です。
ドメイン・エンティティエンティティは、ビジネス・ドメイン・エンティティを表します。ドメイン・エンティティの重要な特徴は、その性質によっ
て識別可能であることです。エンティティにとっては、「どの」エンティティ・オブジェクトが参照されているかが重要になり
ます。
例として、楽器製作店について考えてみます。製作される楽器は識別可能なエンティティで、プレーンJavaクラスと
Java EEでのドメイン駆動 デザインの使用DDDアーティファクトをJava EEコードにマッピングする方法
SEBASTIAN DASCHNER
ORACLE.COM/JAVAMAGAZINE /////////////////////////////////// MAY/JUNE 2018
41
//design patterns /
して実装されます。Java EEでは、Java Persistence API(JPA)によってどのようにエンティティがデータベースに永続化さ
れるかを確認してみると、特におもしろいでしょう。
今回紹介する例は、楽器製作店アプリケーションからの抜粋です。次のコードでは、JPAアノテーション@Entity を使っ
て、識別可能なドメイン・エンティティをデータベースにマッピングしています。JPAのエンティティでは、@Id によってマッ
ピングされる識別子を定義する必要があります。
@Entity
public class ElectricGuitar {
@Id
private long id;
private Model model;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public Model getModel() {
return model;
}
public void setModel(Model model) {
this.model = model;
}
}
ORACLE.COM/JAVAMAGAZINE /////////////////////////////////// MAY/JUNE 2018
42
//design patterns /
楽器のモデルは、埋込みJPAプロパティとしてマッピングされる値オブジェクト(後述)です。
値オブジェクト値オブジェクトは、識別可能な型ではなく、固有の値を表すビジネス・ドメイン型です。こういったドメイン・オブジェクトでは、
ビジネス・プロセス内でどのインスタンスが使われるかは重要ではありません。値オブジェクトの例として、住所、金額、
Javaの列挙型などが挙げられます。値オブジェクトは、不変、したがって再利用可能であることが理想的です。
楽器のモデルも、値オブジェクトの例の1つです。同じブランドと名称によって定義されている楽器のモデルも同一
であり、相互に入れ替えて使用できます。
エンティティには識別子を定義する必要があるため、値オブジェクトは、JPAによって埋込み可能オブジェクトとしてマッ
ピングされます。包含する側のエンティティ型(ここではElectricGuitar)のデータベース表では、埋込み可能型(ここでは
Model)の transientでないすべてのフィールドがインライン化されます。
@Embeddable
public class Model {
@Basic(optional = false)
private String brand;
@Basic(optional = false)
private String name;
protected Model() {
// JPA 用に必要
}
public Model(String brand, String name) {
this.brand = brand;
this.name = name;
}
ORACLE.COM/JAVAMAGAZINE /////////////////////////////////// MAY/JUNE 2018
43
//design patterns /
public String getBrand() {
return brand;
}
public String getName() {
return name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Model model = (Model) o;
return Objects.equals(brand, model.brand)
&& Objects.equals(name, model.name);
}
@Override
public int hashCode() {
return Objects.hash(brand, name);
}
@Override
public String toString() {
return brand + ", " + name;
}
}
ORACLE.COM/JAVAMAGAZINE /////////////////////////////////// MAY/JUNE 2018
44
//design patterns /
値オブジェクトでは、同じインスタンスが同じだと認識されるように、equals とhashCode を実装するのが一般的です。
モデルの実装が完全には不変でないことにお気づきかもしれません。これは、現在のJPA仕様では、可視性が最低で
もprotected である引数のないコンストラクタを定義する必要があるためです。マッピング・フレームワークによっては、
Hibernate のように、privateな引数なしコンストラクタを定義して可視性をさらに制限できるものもあります。ただし、こ
の手順は完全には JPA標準に従っていないため、移植性のないアプリケーションになってしまいます。
サービスサービスは、ドメインのビジネス・ロジックの実行を担います。通常、ビジネス・ロジックはエンティティや値オブジェクト
の一部ではありません。サービスは、ドメイン・エンティティを管理して協調動作させるものであり、ビジネス・ユースケー
スのエントリ・ポイントとなります。また、サービスは、ビジネス・プロセスの個々の手順を結びつけるものでもあります。
Java EEの世界では、サービスはマネージドBean(Contexts and Dependency Injection Bean(CDI Bean)または
EJB Beanのいずれか)として実装されます。ビジネス・ユースケースのエントリ・ポイントとなるサービスは、境界と呼ばれ
ることもあります。このようなサービスは通常、EJB Beanとして実装され、必要となることが多い横断的関心事(トランザ
クションなど)がすでに組み込まれています。
InstrumentCraftShop サービスは、新しい楽器を作成するユースケース境界を表しています。
@Stateless
public class InstrumentCraftShop {
@Inject
InstrumentMaker instrumentMaker;
@PersistenceContext
EntityManager entityManager;
public ElectricGuitar craftInstrument() {
ElectricGuitar instrument = instrumentMaker.build();
return entityManager.merge(instrument);
}
}
ORACLE.COM/JAVAMAGAZINE /////////////////////////////////// MAY/JUNE 2018
45
//design patterns /
境界サービスでは、複雑なドメイン・ロジックを他のサービスに委譲するのが一般的です。このような委譲(たとえば、上
記の InstrumentMaker)は、依存性注入によってBeanに注入します。
集約集約は、複数のエンティティや値オブジェクトからなる複雑なドメイン・エンティティを表すものです。整合性と一貫性を確
保するために、複数のエンティティや値オブジェクトを1つのルート・オブジェクトがまとめて管理し、そのルート・オブジェ
クトを経由してアクセスできるようになっています。
JPAでは、永続化操作は集約のルート・エンティティで呼び出され、集約の他のエンティティにカスケードされます。
次に例を示すのは、集約されたエレキ・ギターの一部となるGuitarBody 型です。ElectricGuitar 型は、集約のルート・
エンティティを表しています。GuitarBody 型は、楽器ドメインの別のエンティティです。
@Entity
public class GuitarBody {
@Id
private long id;
@Enumerated(EnumType.STRING)
@Basic(optional = false)
private Material material;
@Enumerated(EnumType.STRING)
@Basic(optional = false)
private Color color;
protected GuitarBody() {
}
public GuitarBody(Material material, Color color) {
this.material = material;
ORACLE.COM/JAVAMAGAZINE /////////////////////////////////// MAY/JUNE 2018
46
//design patterns /
this.color = color;
}
public enum Material {
MAPLE, MAHOGANY
}
public enum Color {
BLACK, RED
}
}
このドメインでは、エレキ・ギターは1つのギター本体(後ほど追跡できるように、識別可能になっています)で構成されます。
GuitarBody 型はエレキ・ギターから参照され、JPAによって適切にマッピングされます。次に示すのは、永続化でき
るように拡張したElectricGuitar 型です。
@Entity
public class ElectricGuitar {
// 先ほどの id、model、getter、setter の定義
@OneToOne(cascade = CascadeType.ALL, optional = false)
private GuitarBody body;
}
ORACLE.COM/JAVAMAGAZINE /////////////////////////////////// MAY/JUNE 2018
47
//design patterns /
リレーションにカスケードの種類としてALL を指定することで、エレキ・ギターに対して呼び出されるすべての永続化操作
がギター本体(および他にリレーションがあれば、そのリレーション)にカスケードされ、関連エンティティの一貫性が保
証されます。ElectricGuitar は、集約型になります。
リポジトリここまでで説明した永続化操作はすべて、何らかの方法で呼び出す必要があります。同じように、永続化プロバイダから
一貫性のある方法でドメイン・エンティティを取得することも必要です。
DDDリポジトリは、ドメイン・エンティティの永続化管理を担います。リポジトリでは、他のドメイン・モデルが永続
化について細かい実装を行わなくてもよいように、自己充足型で一貫性のある方法で永続化機能がカプセル化されます。
リポジトリ経由で永続化および管理されるのは、ビジネス・ドメイン内で一意の IDを公開しているエンティティのみです。
Java EEと JPAでは、この機能は既存のEntityManager 型ですでに実現されています。このクラスは、エンティティ
またはそのオブジェクト階層として定義されているドメイン・オブジェクトの永続化、取得、管理を行うために使われます。
エンティティが識別子プロパティを定義する必要があるというJPAの制約は、DDDエンティティがビジネス・ドメイン内で
識別可能であるという考え方に合致します。
サービスは、エンティティ・マネージャを注入して使用します。次に例を示します。
@Stateless
public class InstrumentCraftShop {
@Inject
InstrumentMaker instrumentMaker;
@PersistenceContext
EntityManager entityManager;
public ElectricGuitar craftInstrument() {
ElectricGuitar instrument = instrumentMaker.build();
return entityManager.merge(instrument);
}
public ElectricGuitar retrieveInstrument(long identifier) {
ORACLE.COM/JAVAMAGAZINE /////////////////////////////////// MAY/JUNE 2018
48
//design patterns /
return entityManager.find(ElectricGuitar.class,
identifier);
}
}
ファクトリドメイン・オブジェクトの作成には、単にコンストラクタを呼び出すことよりも複雑なロジックが関係する場合もあります。
DDDでは、ファクトリを使ってこれに対処しています。ファクトリの考え方は、複雑なオブジェクトの作成処理を別のメソッ
ドやクラスにカプセル化するというものです。
ドメイン・オブジェクトの作成処理が、ドメイン内に存在するオブジェクトと密結合している場合は、ドメインの型の
メソッドとしてファクトリを定義するとよいでしょう。楽器製作店の例を使用し、楽器に基づいて音楽インスタンスを作って
みます。まず、Music という値オブジェクトを定義します。
public class Music {
private final String description;
public Music(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
この値オブジェクトの作成には、楽器型が密接に関連しています。そのため、ドメイン・オブジェクト型のメソッドとして配
置します。オブジェクトを作成する際に、自身のプロパティに含まれる実際のインスタンスについての情報が必要になる場
合も、同じことが言えます。
public class ElectricGuitar {
// ...
ORACLE.COM/JAVAMAGAZINE /////////////////////////////////// MAY/JUNE 2018
49
//design patterns /
public Music play() {
return new Music("Let's rock!");
}
}
ファクトリを実装する別の方法がCDIプロデューサです。この方法を使うと、特定のドメイン・オブジェクトへの結合度が
低いファクトリとなります。具体的には、CDIプロデューサのメソッドまたはフィールドでインスタンスを公開し、そのインス
タンスをマネージドBeanに注入します。したがって、CDIプロデューサもファクトリと言えます。
ドメイン・イベントドメイン・イベントは、ビジネス・ロジックの実行中に発生します。このイベントはドメインに固有の意味を表すもの
で、通常はビジネス・ユースケースから抽出されます。ドメイン・イベント型の例としては、楽器が製作されたことを示す
InstrumentCrafted や、商品が購入されたことを示すArticlePurchased などが考えられます。
ドメイン・イベントは、イベントに関連付けられた情報を含む値オブジェクトとして実装されます。Javaでは、ドメイ
ン・イベントを不変Plain Old Java Objectとして作成するのが一般的です。イベントは過去にすでに起きたものであるため、
その後に変更されるべきではありません。ElectricGuitarCrafted 型は、通常のJavaオブジェクトとして実装されたドメイン・
イベントです。
public class ElectricGuitarCrafted {
private final Instant instant;
private final Model model;
public ElectricGuitarCrafted(Model model) {
this.model = model;
instant = Instant.now();
}
// getter
ORACLE.COM/JAVAMAGAZINE /////////////////////////////////// MAY/JUNE 2018
50
//design patterns /
}
Java EEには、疎結合なイベント、すなわちCDIイベントを発行および監視できる機能が搭載されています。次に示すように、
CDIイベントはビジネス・ロジックの実行中に発行します。
@Stateless
public class InstrumentCraftShop {
@Inject
InstrumentMaker instrumentMaker;
@Inject
Event<ElectricGuitarCrafted> instrumentCreated;
@PersistenceContext
EntityManager entityManager;
public ElectricGuitar craftInstrument() {
ElectricGuitar instrument = instrumentMaker.build();
instrumentCreated.fire(
new ElectricGuitarCrafted(instrument.getModel()));
return entityManager.merge(instrument);
}
// retrieveInstrument() ...
}
CDI の Event<T> 型はマネージドBeanに注入され、ElectricGuitarCrafted などの任意の定義済みイベントを発行する際
に使われます。イベントは、他のビジネス・ロジックとは切り離されたCDIオブザーバ・メソッドで処理されます。
ORACLE.COM/JAVAMAGAZINE /////////////////////////////////// MAY/JUNE 2018
51
//design patterns /
public class CraftedBrandRecorder {
public void onCraftedInstrument(
@Observes ElectricGuitarCrafted event) {
Model model = event.getModel();
System.out.println(
"new instrument crafted for model: " + model);
}
}
Java EE 8が導入されてからは、直接 CDIでイベントを非同期的に処理できるようになりました。この処理は、
Event#fireAsyncmethodと@ObservesAsyncアノテーションを使って実現されます。この場合、イベント処理は別のスレッ
ドで実行されます。
まとめ最新のJava EEを使うと、ビジネス・ロジックを重視したエンタープライズ・アプリケーションを開発できます。J2EEとは異なり、
このテクノロジーによってドメイン・ロジックが多くの制約を受けることはありません。ドメインのクラスでは、特定のJava
EE 型の拡張や実装を行う必要はありません。もっとも簡単なアプローチは、ビジネス・ロジックをプレーンJavaで記述し、
技術的な横断的関心事をアノテーションで構成することです。
CDIおよびJPAの仕様が持つ柔軟性により、開発者はアプリケーションに価値を追加するもの、すなわちビジネス・
ロジックに集中できます。なお、Jakarta EEは Java EE 8 をベースにしたものになる予定です。そのため、Java EEの背景
にある概念や考え方、また本記事で示した内容は、今後も変わることはないはずです。</article>
Sebastian Daschner(@DaschnerS):コンサルタント、著者、トレーナーとして活躍しているJava Champion。『Architecting Modern Java EE Applications』を執筆し、JAX-RS、JSON-P、Configの各専門家グループに参加。
複数のオープンソース・プロジェクトにも携わり、JavaOne Rockstarを2回受賞している。