0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DDD練習1-上級-オンライン学習プラットフォーム

0
Posted at

要件

オンライン学習プラットフォームを設計してください。

  • 講師はコースを作成できる。コースにはタイトル、説明、カテゴリ、価格がある。
  • コースは複数のセクションで構成され、各セクションには複数のレッスンがある。
  • レッスンには「動画レッスン」と「テストレッスン」の 2 種類がある。動画レッスンには動画URL と再生時間がある。テストレッスンには問題リスト(複数選択式)がある。
  • コースは「下書き」→「レビュー中」→「公開」のステータスを持つ。レビューは運営が行い、品質基準を満たさない場合は差し戻される。
  • 受講者はコースを購入して受講を開始する。購入時にコースの価格がスナップショットとして記録される。
  • 受講者はレッスンを順番に進め、各レッスンの完了状態が記録される。テストレッスンは正答率 80% 以上で合格。
  • 全レッスンを完了すると修了証が発行される。修了証には受講者名、コース名、修了日が記載される。
  • 受講者はコースにレビュー(1〜5の星評価+コメント)を投稿できる。レビューは1コースにつき1件まで。
  • 講師には売上の 70% が報酬として支払われる。報酬は月次で集計される。

所感

指摘された内容について可能な限り意識して取り組んで入るが、まだまだと感じるなと思ってます
今回気になったのはEnrollmentが抜けているよという話だったんだけど、この点は受講生の集約にまとめたつもりでした
ただ、「一緒に変更されるもの」ではないので、その点の境界の分割が弱いという自己評価になりました

模範解答

エンティティ

エンティティ 識別子 主な属性・関連
講師 (Instructor) instructorId 氏名, プロフィール
コース (Course) courseId タイトル, 説明, カテゴリ, 価格, ステータス, 講師
セクション (Section) sectionId タイトル, 表示順序
レッスン (Lesson) lessonId タイトル, 表示順序, レッスン種別
受講者 (Student) studentId 氏名, メールアドレス
受講登録 (Enrollment) enrollmentId 受講者, コース, 購入価格, 進捗, 修了状態
レビュー (Review) reviewId 受講者, コース, 星評価, コメント
報酬明細 (PayoutRecord) payoutId 講師, 対象年月, 金額, 支払ステータス

値オブジェクト

値オブジェクト 構成要素 理由
コースステータス (CourseStatus) 下書き/レビュー中/公開/差し戻し 列挙型
動画情報 (VideoContent) 動画URL, 再生時間 動画レッスンの内容詳細
テスト内容 (QuizContent) 問題リスト(問題文, 選択肢, 正解) テストレッスンの内容詳細
レッスン進捗 (LessonProgress) レッスンへの参照, 完了フラグ, テストスコア 受講登録内の一行
修了証 (Certificate) 受講者名, コース名, 修了日 発行後は不変。独自ライフサイクル不要
金額 (Money) 数値, 通貨 汎用値オブジェクト
星評価 (Rating) 1〜5 の整数 バリデーション付きの値
合格基準 (PassingCriteria) 正答率の閾値(80%) ポリシーを明示化

集約と境界の理由

集約ルート 含まれるもの 境界の理由
Course Course + Section + Lesson (+ VideoContent / QuizContent) コースの構成(セクション・レッスン)は一貫性を持って変更される必要がある。セクションやレッスンが単独で存在する意味はない。
Enrollment Enrollment + LessonProgress のリスト 進捗管理は受講者×コースの単位で整合性が必要。「全レッスン完了 → 修了」の判定はこの集約内で行う。
Review Review 単体 「1コース1レビュー」の制約はあるが、コースや受講登録とは独立してライフサイクルを持つ。
Instructor Instructor 単体 講師プロフィールは独立管理。
Student Student 単体 受講者情報は独立管理。
PayoutRecord PayoutRecord 単体 報酬は月次集計の結果であり、他の集約とトランザクションを共有しない。

ドメインイベント

  • CourseCreated — コースが作成された
  • CourseSubmittedForReview — コースがレビューに提出された
  • CoursePublished — コースが公開された
  • CourseRejected — コースが差し戻された
  • CoursePurchased — コースが購入された(→ Enrollment 作成のトリガー)
  • LessonCompleted — レッスンが完了した
  • QuizPassed / QuizFailed — テストの合否
  • CourseCompleted — 全レッスン完了(→ 修了証発行のトリガー)
  • CertificateIssued — 修了証が発行された
  • ReviewPosted — レビューが投稿された

ドメインサービス

サービス 責務
テスト採点サービス (QuizGradingService) 受講者の回答を受け取り、正答率を計算し、合格基準と照合する。Lesson にも Enrollment にも属しにくい横断ロジック。
報酬集計サービス (PayoutCalculationService) 月次で売上を集計し、70% の報酬額を算出して PayoutRecord を生成する。複数の集約(Enrollment の購入価格)を参照する。
修了証発行サービス (CertificateIssuanceService) CourseCompleted イベントを受けて修了証を生成する。受講者名・コース名の取得が集約をまたぐため、サービスとして切り出す。

解説ポイント

  • Course 集約を大きくしすぎない判断: Section と Lesson は Course に従属するが、受講者数が多い場合に Course 集約が肥大化する。Enrollment(受講登録)を別集約にすることで、コース定義と受講進捗の更新が競合しない。
  • 修了証をエンティティではなく値オブジェクトにする理由: 修了証は一度発行されたら変更されない。再発行が必要なら新規作成する。独自の ID で管理する必要がなければ値オブジェクトで十分(※ 要件次第ではエンティティもあり得る)。
  • Review の「1コース1件」制約: この制約は Review 集約単体では保証できない。ドメインサービスまたはリポジトリレベルで一意性を担保する設計が必要。

自分の解答

ユビキタス言語

オンライン学習プラットフォーム
コース情報(タイトル,説明,カテゴリ,価格)
コース状態(下書き,レビュー中,公開)
動画レッスン(URL,再生時間)
テストレッスン(問題リスト)
講師, 運営, 品質基準, 受講者
購入する, レッスンの完了状態(テストでは80%)
修了証(コース名、終了日)
コースレビュー
売上(70%, 月次で集計)

値オブジェクト

値オブジェクト 構成要素 理由
コース詳細 タイトル,説明,カテゴリ,価格 最低限の値群で構成
コース状態 下書き,レビュー状態,公開 コースの情報ではないので分ける
動画レッスン URL,再生時間
テストレッスン 問題集
コースレビュー レビューテキスト 内容のバリデーションが必要

エンティティ

エンティティ 識別子 主な属性・関連
コース情報 コース情報ID コース詳細, コース状態
コース コースID 動画レッスン, テストレッスン
コースコメント コースコメントID コメント,受講生ID,日時
受講生 受講生ID
講師 講師ID
修了証 修了証ID コースID, 終了日

集約

コースという集約において、講師と受講生では領域が異なる。共通部分をコース集約にまとめて、各々しか関係ない領域は各々がIDを所有する仕組みが良いと考える。

集約ルート 含まれるもの 整合性のルール
コース コース,コース情報ID,コースコメントID
受講生 受講生,コース,修了証ID
講師 講師,コース

ドメインイベント

  • コースが作成された
  • コースの品質基準を判定した
  • コースが購入された
  • コースが終了した
  • 月次になった
  • コースのレビューコメントが投稿された

ドメインサービス

サービス 責務
レビューサービス 作成されたコースのレビューを行う
修了証作成サービス
報酬支払いサービス 講師に70%の報酬を支払う

フィードバック・改善点

良くなった点

  • ユビキタス言語の抽出を最初にやった(判断基準リストの手順①を実践できている)
  • 集約の設計で「講師と受講生では領域が異なる」と認識できている(境界付けられたコンテキストの感覚に近い)
  • ドメインイベントの数が増え、過去形で書けている
  • ドメインサービスを名詞で命名できている(レビューサービス、修了証作成サービス)
  • 集約間のID参照を意識できている(「各々がIDを所有する」という記述)

改善点 1: 「コース情報」と「コース」が分かれている

自分の解答:
  コース情報(エンティティ)= コース詳細 + コース状態
  コース(エンティティ)= 動画レッスン + テストレッスン

模範解答:
  Course(エンティティ)= タイトル, 説明, カテゴリ, 価格, ステータス
    └── Section → Lesson(動画/テスト)

「コース情報」と「コース」は同じ概念を分割してしまっている。1回目初級の「アカウント ≒ 顧客」と同じパターン。コースの メタ情報(タイトル等)もコンテンツ(レッスン)も、同じ Course エンティティの責務

改善点 2: セクション(Section)が抜けている

要件に「コースは複数のセクションで構成され、各セクションには複数のレッスンがある」とあるので、構造は以下。

Course
  └── Section(sectionId で識別 → エンティティ)
        └── Lesson(lessonId で識別 → エンティティ)
              ├── VideoContent(値オブジェクト)
              └── QuizContent(値オブジェクト)

要件の名詞リストアップ時に「セクション」が漏れている。

改善点 3: 「受講登録(Enrollment)」という概念が抜けている

今回の最大のポイント。受講者がコースを購入すると 受講登録 が生まれ、ここに進捗や購入価格のスナップショットが記録される。

× 受講生集約にコースを含める
  → 受講生が10コース買ったら集約が肥大化する

○ Enrollment(受講登録)を独立した集約にする
  → 受講者ID + コースID の参照だけ持つ
  → 進捗(LessonProgress)はここで管理
  → 購入価格のスナップショットもここ

「受講生がコースを受講する」という関係自体がエンティティになる パターン。2つのエンティティの間の「関係」や「行為の記録」が、独自のIDとライフサイクルを持つ場合、それ自体がエンティティになる。

改善点 4: 値オブジェクトの判断ミス

自分の解答 問題点
コース詳細(タイトル,説明,カテゴリ,価格) これらは Course エンティティの属性。セットにする意味が薄い
コースレビュー → 値オブジェクト レビューはIDで追跡し、1コース1件の制約を管理する必要がある → エンティティ

逆に以下が値オブジェクトとして抜けている。

抜けている値オブジェクト 判断基準のどれ?
LessonProgress(完了フラグ, スコア) ① セット
Certificate(受講者名, コース名, 修了日) ① セット + 発行後不変
Rating(1〜5の整数) ② バリデーション
PassingCriteria(80%) ② バリデーション(ポリシーの明示化)
Money(数値, 通貨) ③ 計算

改善点 5: 集約に他の集約のオブジェクトを含めている

自分の解答:
  受講生集約 = 受講生 + コース + 修了証ID
  講師集約   = 講師 + コース

模範解答:
  Student集約     = Student 単体
  Instructor集約  = Instructor 単体
  Enrollment集約  = Enrollment + LessonProgress(受講者×コースの関係)

受講生集約に「コース」を含めると、受講生のプロフィール変更時にコースの進捗データもロックされる。「一緒に変更されるか?」 で考えると、受講生のプロフィール変更と学習進捗の更新は別の操作なので、分離すべき。

集約間はIDの参照のみ。オブジェクトを直接含めない。

改善点 6: ドメインイベント

自分の解答:                           模範解答との差分:
コースが作成された                     ○ CourseCreated
コースの品質基準を判定した              → CoursePublished / CourseRejected に分ける
コースが購入された                     ○ CoursePurchased
コースが終了した                       ○ CourseCompleted
月次になった                          → 時間の経過はドメインイベントではない
コースのレビューコメントが投稿された     ○ ReviewPosted
                                     ✗ LessonCompleted が抜けている
                                     ✗ CertificateIssued が抜けている
                                     ✗ QuizPassed / QuizFailed が抜けている
  • 「品質基準を判定した」は結果が2通り(公開 or 差し戻し)あるので、結果ごとに別イベント にする方がトリガーとして使いやすい
  • 「月次になった」は時間の経過であり、ビジネス上の出来事ではない。スケジューラのトリガーであってドメインイベントではない
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?