Javaにおけるドメインモデルパターン:堅牢なビジネスロジックの構築
別名
- 概念モデル
- ドメインオブジェクトモデル
ドメインモデルデザインパターンの意図
ドメインモデルパターンは、ソフトウェア内に、それが表現するように設計された現実世界のシステムに対応する概念モデルを作成することを目的としています。これには、アプリケーションドメインに関連するデータと動作の両方をカプセル化する豊富なドメインオブジェクトを使用し、ビジネスロジックが集中化されるようにすることが含まれます。
現実世界の例を用いたドメインモデルパターンの詳細な説明
現実世界の例
ドメインモデルデザインパターンを使用するオンライン書店システムを考えてみましょう。このシステムでは、
Book
、Author
、Customer
、Order
などのさまざまなドメインオブジェクトが、コアビジネスロジックとルールをカプセル化します。たとえば、Book
オブジェクトには、タイトル、著者、価格、在庫数量などの属性と、これらの属性を管理するメソッドが含まれます。Order
オブジェクトは、注文の詳細を管理し、合計価格を計算し、在庫の有無を確認します。このアプローチにより、ビジネスロジックがドメインオブジェクト内に集中化され、システムのモジュール性が向上し、保守が容易になり、新しい機能が追加されてもスケーラブルになります。
平易な言葉で
ドメインモデルは、動作とデータの両方を組み込んだドメインのオブジェクトモデルです。
Javaにおけるドメインモデルパターンのプログラミング例
eコマースWebアプリケーションを構築する必要があると仮定しましょう。要件を分析すると、繰り返し話題になる名詞がいくつかあることに気づくでしょう。それはあなたの顧客であり、顧客が探す製品です。これら2つはドメイン固有のクラスであり、各クラスにはドメイン固有のビジネスロジックが含まれます。
eコマースアプリの例では、製品を購入し、必要に応じて返品したい顧客のドメインロジックを処理する必要があります。ドメインモデルパターンを使用して、Customer
クラスとProduct
クラスを作成できます。そのクラスのすべてのインスタンスは動作とデータの両方を組み込み、基になるテーブルの1つのレコードのみを表します。
public class Customer {
// Customer properties and methods
}
public class Product {
// Product properties and methods
}
データアクセスオブジェクト(DAO):これらのオブジェクトは、データベースへの抽象インターフェースを提供します。ドメインエンティティを取得し、変更をデータベースに保存するために使用されます。提供されたコードでは、CustomerDaoImpl
とProductDaoImpl
がDAOです。
public class CustomerDaoImpl implements CustomerDao {
// Implementation of the methods defined in the CustomerDao interface
}
public class ProductDaoImpl implements ProductDao {
// Implementation of the methods defined in the ProductDao interface
}
ドメインロジック:これはドメインエンティティ内にカプセル化されています。たとえば、Customer
クラスには、顧客が実行できるアクションを表すbuyProduct
やreturnProduct
などのメソッドがあります。
public class Customer {
// Other properties and methods...
public void buyProduct(Product product) {
// Implementation of buying a product
}
public void returnProduct(Product product) {
// Implementation of returning a product
}
}
アプリケーション:App
クラスは、ドメインエンティティとそのメソッドを使用して、アプリケーションのビジネスロジックを実装します。
public class App {
public static final String H2_DB_URL = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1";
public static final String CREATE_SCHEMA_SQL =
"CREATE TABLE CUSTOMERS (name varchar primary key, money decimal);"
+ "CREATE TABLE PRODUCTS (name varchar primary key, price decimal, expiration_date date);"
+ "CREATE TABLE PURCHASES ("
+ "product_name varchar references PRODUCTS(name),"
+ "customer_name varchar references CUSTOMERS(name));";
public static final String DELETE_SCHEMA_SQL =
"DROP TABLE PURCHASES IF EXISTS;"
+ "DROP TABLE CUSTOMERS IF EXISTS;"
+ "DROP TABLE PRODUCTS IF EXISTS;";
public static void main(String[] args) throws Exception {
// Create data source and create the customers, products and purchases tables
final var dataSource = createDataSource();
deleteSchema(dataSource);
createSchema(dataSource);
// create customer
var customerDao = new CustomerDaoImpl(dataSource);
var tom =
Customer.builder()
.name("Tom")
.money(Money.of(USD, 30))
.customerDao(customerDao)
.build();
tom.save();
// create products
var productDao = new ProductDaoImpl(dataSource);
var eggs =
Product.builder()
.name("Eggs")
.price(Money.of(USD, 10.0))
.expirationDate(LocalDate.now().plusDays(7))
.productDao(productDao)
.build();
var butter =
Product.builder()
.name("Butter")
.price(Money.of(USD, 20.00))
.expirationDate(LocalDate.now().plusDays(9))
.productDao(productDao)
.build();
var cheese =
Product.builder()
.name("Cheese")
.price(Money.of(USD, 25.0))
.expirationDate(LocalDate.now().plusDays(2))
.productDao(productDao)
.build();
eggs.save();
butter.save();
cheese.save();
// show money balance of customer after each purchase
tom.showBalance();
tom.showPurchases();
// buy eggs
tom.buyProduct(eggs);
tom.showBalance();
// buy butter
tom.buyProduct(butter);
tom.showBalance();
// trying to buy cheese, but receive a refusal
// because he didn't have enough money
tom.buyProduct(cheese);
tom.showBalance();
// return butter and get money back
tom.returnProduct(butter);
tom.showBalance();
// Tom can buy cheese now because he has enough money
// and there is a discount on cheese because it expires in 2 days
tom.buyProduct(cheese);
tom.save();
// show money balance and purchases after shopping
tom.showBalance();
tom.showPurchases();
}
private static DataSource createDataSource() {
var dataSource = new JdbcDataSource();
dataSource.setUrl(H2_DB_URL);
return dataSource;
}
private static void deleteSchema(DataSource dataSource) throws SQLException {
try (var connection = dataSource.getConnection();
var statement = connection.createStatement()) {
statement.execute(DELETE_SCHEMA_SQL);
}
}
private static void createSchema(DataSource dataSource) throws SQLException {
try (var connection = dataSource.getConnection();
var statement = connection.createStatement()) {
statement.execute(CREATE_SCHEMA_SQL);
}
}
}
プログラムの出力
12:17:23.834 [main] INFO com.iluwatar.domainmodel.Customer -- Tom balance: USD 30.00
12:17:23.836 [main] INFO com.iluwatar.domainmodel.Customer -- Tom didn't bought anything
12:17:23.841 [main] INFO com.iluwatar.domainmodel.Customer -- Tom want to buy Eggs($10,00)...
12:17:23.842 [main] INFO com.iluwatar.domainmodel.Customer -- Tom bought Eggs!
12:17:23.842 [main] INFO com.iluwatar.domainmodel.Customer -- Tom balance: USD 20.00
12:17:23.842 [main] INFO com.iluwatar.domainmodel.Customer -- Tom want to buy Butter($20,00)...
12:17:23.843 [main] INFO com.iluwatar.domainmodel.Customer -- Tom bought Butter!
12:17:23.843 [main] INFO com.iluwatar.domainmodel.Customer -- Tom balance: USD 0.00
12:17:23.843 [main] INFO com.iluwatar.domainmodel.Customer -- Tom want to buy Cheese($20,00)...
12:17:23.843 [main] ERROR com.iluwatar.domainmodel.Customer -- Not enough money!
12:17:23.843 [main] INFO com.iluwatar.domainmodel.Customer -- Tom balance: USD 0.00
12:17:23.843 [main] INFO com.iluwatar.domainmodel.Customer -- Tom want to return Butter($20,00)...
12:17:23.844 [main] INFO com.iluwatar.domainmodel.Customer -- Tom returned Butter!
12:17:23.844 [main] INFO com.iluwatar.domainmodel.Customer -- Tom balance: USD 20.00
12:17:23.844 [main] INFO com.iluwatar.domainmodel.Customer -- Tom want to buy Cheese($20,00)...
12:17:23.844 [main] INFO com.iluwatar.domainmodel.Customer -- Tom bought Cheese!
12:17:23.846 [main] INFO com.iluwatar.domainmodel.Customer -- Tom balance: USD 0.00
12:17:23.846 [main] INFO com.iluwatar.domainmodel.Customer -- Tom bought: Eggs - $10.00, Cheese - $20.00
現実世界の例を用いたドメインモデルパターンの詳細な説明

Javaでドメインモデルパターンを使用するタイミング
- 豊富なビジネスロジックを持つ複雑なアプリケーションに適しています。
- ビジネスロジックまたはドメインの複雑度が高く、現実世界のエンティティとその関係を密接に表現するモデルが必要な場合。
- ドメインエキスパートが開発プロセスに関与し、モデルがドメインの概念を正確に反映していることを確認する必要があるアプリケーションに適しています。
ドメインモデルパターン Javaチュートリアル
Javaにおけるドメインモデルパターンの現実世界での応用
- エンタープライズアプリケーション(ERP、CRMシステム)
- 金融システム(銀行、取引プラットフォーム)
- ヘルスケアアプリケーション(患者記録管理)
- eコマースプラットフォーム(製品カタログ、ショッピングカート)
ドメインモデルパターンの利点とトレードオフ
利点
- コミュニケーションの改善:開発者とドメインエキスパートに共通言語を提供し、理解とコラボレーションを強化します。
- 柔軟性:ドメインエンティティ内にビジネスロジックをカプセル化し、システムの他の部分に影響を与えることなく、変更と拡張を容易にします。
- 保守性:適切に構造化されたドメインモデルは、アプリケーションの長期的な保守と進化を簡素化できます。
- 再利用性:ドメインクラスは、同じドメイン内のさまざまなプロジェクトで再利用できることがよくあります。
トレードオフ
- 複雑性:特にドメインモデルが過剰になる可能性がある単純なアプリケーションでは、複雑性が増す可能性があります。
- パフォーマンス上の懸念:複雑な動作を持つ豊富なドメインオブジェクトは、パフォーマンスのボトルネックにつながる可能性があり、慎重な最適化が必要です。
- 学習曲線:ドメインを十分に理解する必要があり、ドメインの概念に不慣れな開発者にとっては急な学習曲線になる可能性があります。
関連するJavaデザインパターン
- データアクセスオブジェクト(DAO):データソースへのすべてのアクセスを抽象化およびカプセル化するため。
- リポジトリ:ドメインとデータマッピングレイヤーの間を仲介し、インメモリドメインオブジェクトコレクションのように機能します。
- サービスレイヤー:利用可能な操作のセットを確立し、各操作でのアプリケーションの応答を調整するサービスレイヤーを使用して、アプリケーションの境界を定義します。
- ユニットオブワーク:ビジネストランザクションの影響を受けるオブジェクトのリストを保持し、変更の書き出しを調整します。