アプリにShop
エンティティがあるとします。そして、2種類のユーザー:Customer
とAdmin
。顧客がショップで何かを購入すると、その顧客の訪問がシステムに登録されます(customer-shopごとに訪問カウンターがあります)。
したがって、ショップが次のようにモデル化されていると仮定します。
class Shop {
id: number;
name: string;
postalAddress: PostalAddress;
categories: Category[];
}
しかし、顧客の観点からは、プロパティnumberOfVisits: number
Shop
エンティティでの使用も非常に有効です。
class Shop {
id: number;
name: string;
postalAddress: PostalAddress;
categories: Category[];
numberOfVisits: number; // this makes sense for customers
}
これは、実行されているユースケースによって異なります。
ユースケース 'A':管理ユーザーとして、システムに存在するすべてのショップを一覧表示したいと思います。
この場合、訪問カウンタなしでShop
エンティティを処理することで問題ありません。
ユースケース 'B':顧客ユーザーとして、私が訪問したすべてのショップ(訪問数を含む)をリストしたいと思います。
この場合、顧客はいくつかのショップがリストされていることを期待し、訪問数に関心があります。したがって、Shop
プロパティを持つnumberOfVisits
エンティティを扱うことが重要になります。
私はいくつかの設計アプローチに出くわしましたが、どれもまだ私を納得させていません。
すべてのケースの訪問カウンタプロパティを持つ一意のShop
エンティティを持ちます。
class Shop {
id: number;
name: string;
postalAddress: PostalAddress;
categories: Category[];
numberOfVisits: number;
}
このアプローチについて私が気に入らないのは、特定のユースケースやアプリケーションコンテキスト(null
に設定されている場合でも)では意味をなさないフィールドを持つエンティティが1つあることです。とてもきれいな解決策だとは思いません。
いくつかのShop
エンティティー:
class Shop {
id: number;
name: string;
postalAddress: PostalAddress;
categories: Category[];
}
class ShopWithVisits {
id: number;
name: string;
postalAddress: PostalAddress;
categories: Category[];
numberOfVisits: number;
}
あるいは:
class Shop {
id: number;
name: string;
postalAddress: PostalAddress;
categories: Category[];
numberOfVisits: number;
}
class ShopWithVisits extends Shop {
numberOfVisits: number;
}
これは#1よりも優れていると思います。なぜなら、異なるユースケースやコンテキストでの同じエンティティの異なる表現について話すからです。しかし、それでも、これが何らかの形で改善できるかどうかはわかりません。
これについてどう思いますか?あなたはどちらのアプローチが良いと思いますか、そしてその理由は?デザインを改善するために私が従うことができる他のアプローチはありますか?
典型的な「レイヤード」デザインに基づいた質問への回答を提供します。
説明をアプリケーション設計のコンテキストに入れてみましょう。 「データ」と「サービス」の部分を区別する必要があります。ユースケースで説明したように「クエリ」を実行するとは、ストレージから情報を取得し、データモデルの一部を再試行することを意味します。
通常、データモデル自体はサービスを認識していないため、ここで依存関係を検討する必要がある設計を行います。
例えば:
つまり、「顧客」と「ショップ」のデータクラスは、訪問の概念についても知らないはずです。これにより、下の図のようなクラスデザインが作成されます。
青色でデータモデルを確認できます。
そのように設計することが重要です。そうしないと、何百万回もの訪問をクラスインスタンスにロードしないと、ショップまたは顧客のリストをロードできません。
クエリ自体は「サービス」を介して処理でき、一部のストレージにアクセスします。ストレージ自体は、必要なデータを受信するためのメソッドを備えています。
私の意見では、顧客ごとにショップを用意することは、ショップデータを不必要に複製することです。さらに、私はその場合に継承を使用することに消極的です。
訪問を記録する場合は、以下のようなすべての訪問エントリを保持するクラスを用意してください(構文を許してください。使用している言語がわかりません)。
class CustomerVisitEntry
{
shop: Shop;
customer: Customer;
dateVisit: DateTime;
itemsPurchased: ICollection<ShopItem>;
//literally any useful information about a visit
}
class CustomerVisitLog
{
entries: ICollection<CustomerVisitEntry>
}
別のアプローチを提案します。両方のDDDですが、特にOOはモデリングについてですbehavior)。データにShop
を使用する代わりに、Shop
がサポートするすべてのメソッドに集中する必要があります。メソッド名も、ユビキタス言語、つまりビジネス言語に由来する必要があります。
メソッドの1つのセットが使用されている完全に異なるコンテキストでオブジェクトが使用されており、別のコンテキストで完全に異なるセットが使用されている場合は、オブジェクトが「分割」されていることを示しています。 DDD用語では、おそらく「境界付きコンテキスト」を明らかにしました。
まだデータがまったくないことに注意してください。これで、モデルがドメインとドメインの動作のほとんどを表現したので、オブジェクトに必要なデータを追加できます。重複や非正規化についてあまり心配しないでください。コードとデータベースでは完全に異なる意味があります。
ユースケースに基づいて、次のようなデータを設定します。
class Shop {
static allShops: [Shop]
id: number
name: string
postalAddress: PostalAddress
categories: [Category]
}
class User {
id: number
visited: [Shop] // as an unordered set
}
次に、AdminクラスはShop.allShops
を呼び出してシステム内のすべてのショップのリストを取得し、Userクラスはself.visited
を呼び出して訪問したすべてのショップのリストを取得できます。
どちらのユースケースにも、訪問数が重要であることを意味するものは何もないため、モデル化する理由はありません。
他のユースケースで訪問数が重要な場合は、おそらく次のようにモデルを拡張します。
class User {
id: Int;
timesVisited: [Shop: Int]
}