要件
個人間で商品を売買できるフリマアプリを設計してください。
- ユーザーはアカウントを作成し、出品者にも購入者にもなれる。
- 出品者は商品を出品する。商品には商品名、説明文、カテゴリ、商品画像(複数可)、価格がある。
- 商品のステータスは「出品中」→「取引中」→「売却済み」と遷移する。
- 購入者は商品を購入する。購入時に商品価格分の残高が購入者から引かれる。
- ユーザーには残高があり、チャージ(入金)ができる。残高が不足している場合、購入は失敗する。
- 購入後、出品者は商品を発送する。購入者が受け取り確認をすると取引が完了する。
- 取引完了時、商品価格から手数料 10% を差し引いた金額が出品者の残高に加算される。
- 購入者は取引完了後に出品者を評価できる(良い・普通・悪い+コメント)。評価は取引1件につき1回のみ。
問い: エンティティ、値オブジェクト、集約、ドメインイベントを抽出してください。
所感
工夫点としてユビキタス言語を最初にして洗い出すことができた点はDDD練習1で得た経験かなと印象です。
あとは、練習1と同じミスが散見されるので、意識する必要がありそう
難しさとして意識するのは集約の範囲定義で「一緒に変更されるか?」という概念分割の意識と、リストを独立集約にするというごく基礎の概念を忘れないことだと思った。
昔のListを特に考えずにメンバ変数するという意識が残っているように思う...。
-
「商品」と「取引」を1つにまとめてしまっている
解答を見ると分かるが、Entityの区切る感覚が未熟だなという印象 -
残高を値オブジェクトにした
金額というオブジェクトにしたかったが、なぜか思いつかなかった。 -
評価(Rating)がエンティティとして抜けている
取引評価ということでValueObjectのところで記載した。IDを持たないようにした結果、User AggeregateでListを持つ構成となったので、その時点で違和感を感じたら良かった -
集約の境界
↑と一緒ですねListについて意識が浅かったです。IDを用いて粗結合をちゃんと理解しないとだめだと反省中。。。
模範解答
エンティティ
| エンティティ | 識別子 | 主な属性・関連 |
|---|---|---|
| ユーザー (User) | userId | ニックネーム, メールアドレス, 残高 |
| 商品 (Item) | itemId | 商品名, 説明文, カテゴリ, 画像リスト, 価格, ステータス, 出品者 |
| 取引 (Transaction) | transactionId | 商品, 購入者, 出品者, 取引ステータス, 購入価格 |
| 評価 (Rating) | ratingId | 取引, 評価者, 被評価者, 評価ランク, コメント |
値オブジェクト
| 値オブジェクト | 構成要素 | 理由 |
|---|---|---|
| 金額 (Money) | 数値, 通貨 | 同額なら区別不要 |
| 商品画像 (ItemImage) | 画像URL, 表示順序 | 個別に追跡する必要がない |
| 商品ステータス (ItemStatus) | 出品中/取引中/売却済み | 列挙型 |
| 取引ステータス (TransactionStatus) | 支払済み/発送済み/受取完了 | 列挙型 |
| 評価ランク (RatingGrade) | 良い/普通/悪い | 列挙型 |
| カテゴリ (Category) | カテゴリ名 | 単純な分類値(※ カテゴリ管理が複雑になるならエンティティに昇格) |
| 手数料 (Commission) | 料率(10%), 計算結果の金額 | 計算ルールを明示化 |
集約
| 集約ルート | 含まれるもの | 整合性のルール |
|---|---|---|
| User | User 単体 | 残高は 0 以上。チャージ・引き落とし・売上入金の整合性 |
| Item | Item + ItemImage | 商品ステータスの遷移ルール。出品中の商品のみ購入可能 |
| Transaction | Transaction 単体 | 取引ステータスの遷移は一方向。購入価格はスナップショット |
| Rating | Rating 単体 | 1取引1評価の制約 |
ドメインイベント
-
ItemListed— 商品が出品された -
ItemPurchased— 商品が購入された(→ 残高引き落とし・取引作成のトリガー) -
ItemShipped— 商品が発送された -
DeliveryConfirmed— 受け取り確認された(→ 出品者への売上入金トリガー) -
SellerRated— 出品者が評価された -
BalanceCharged— 残高がチャージされた
解説ポイント
- 購入価格のスナップショット: 商品の価格は出品者が変更できるため、取引には購入時点の価格を記録する。Transaction が Item の価格を直接参照すると、後からの価格変更で不整合が起きる。
- 残高操作の整合性: 購入時の「残高引き落とし」と取引完了時の「売上入金」は別トランザクション。結果整合性(イベント駆動)で実現するのが自然。
- Item と Transaction を分ける理由: 商品情報の管理(画像追加・説明変更)と取引の進行は独立した関心事。同一集約にすると不要なロック競合が発生する。
自分の解答
ユビキタス言語
ユーザー, アカウント, 購入者, 出品者, 商品(名前, 説明文, カテゴリ, 画層s, 価格)
ステータス(出品中, 取引中, 売却済み), 残高, チャージ, 発送, 受け取り確認, 手数料, 出品者評価
Value Object
| 値オブジェクト | 構成要素 | 理由 |
|---|---|---|
| ステータス | 出品中, 取引中, 売却済み | 状態保持する必要がある |
| 残高 | 金 | ユーザーエンティティに入れるには変更タイミングが異なる |
| 手数料 | 割合 | 任意のタイミングで変わる可能性がある |
| 発送先 | 住所 | 購入者の住所と異なる可能性がある |
| 取引評価 | 良い・普通・悪い+コメント | |
Entity
| エンティティ | 識別子 | 主な属性・関連 |
|---|---|---|
| 取引商品 | 取引ID | 名前, 説明文, カテゴリ, 画像s, 価格 |
| ユーザー | アカウント | ユーザー名, e-mail |
Aggregate
| 集約ルート | 含まれるもの | 整合性のルール |
|---|---|---|
| ユーザー | ユーザー, 残高, 購入評価リスト, 出品評価リスト | アカウント |
| 取引商品 | 取引商品, ステータス, 発送先 | 取引ID |
Domain Event
- 購入された
- チャージされた
- 受け取り確認された
- 取引が完了した
- 評価化された
Domain Service
| サービス | 責務 |
|---|---|
| 取引サービス | 複数の同時購入が発生しないようにする |
| 発送サービス | お互いの住所、名前が知られなように中継する |
| 購入確定サービス | 残高が不足していたら購入させない |
| 出品者評価サービス | |
| 残高加算サービス | 取引後の出品者へ残高を加算する |