コンテンツにスキップ

ありがとうございます。

ここからがこのシステムの設計で最も重要な部分になります。

前回のDB設計でも十分実装できますが、実際のLMS(Learning Management System)では以下の問題が発生します。

公開済み教材を修正すると、受講者が見ている教材まで変更されてしまう

例えば

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Java入門

公開中

↓

教師が誤字を修正

↓

保存

↓

受講中の受講者にも即反映

これは企業向け教材ではほぼNGです。

そのため、多くのLMSでは

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
公開版

↓

コピー

↓

編集

↓

審査

↓

公開

↓

公開版を切り替える

という運用をしています。


第3段階 設計改善(実運用レベル)

全体イメージ

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Course
   │
   ├ Section
   │
   ├ Lesson
   │
   └ Content
           │
           ▼
    ContentVersion
           │
     ├ Video
     ├ Text
     ├ Quiz
     └ File

ポイントは

Contentを親

ContentVersionを子

にすることです。


なぜVersionを作るのか

例えば

1
Javaとは?

という教材があるとします。

Content

1
2
3
ID=100

タイトル=Javaとは?

Version

1
2
3
v1

公開中

教師が修正

1
2
3
v2

下書き

受講者は

1
v1

を見ています。

管理者が承認すると

1
2
3
v2

公開

になります。

つまり

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
公開中

↓

編集

↓

審査

↓

公開切替

ができます。


Content

Contentは固定情報だけ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
id

lesson_id

content_type

author_id

current_version

created_at

ContentVersion

ここに本文を持たせます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
id

content_id

version_no

title

summary

status

published_at

created_at

status

1
2
3
4
5
6
7
8
9
DRAFT

REVIEW

APPROVED

PUBLISHED

REJECTED

Video

1
2
3
4
5
6
7
8
9
VideoContent

version_id

video_type

video_url

video_id

Versionに紐付きます。


Text

1
2
3
4
5
6
7
TextContent

version_id

body

format

Markdownなら

1
2
3
4
5
6
7
8
9
Markdown

↓

HTML

↓

表示

Quiz

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
QuizContent

version_id

question

category

answer_count

multiple_answer

Choice

1
2
3
4
5
6
7
8
9
QuizChoice

quiz_id

choice

correct

order

File

1
2
3
4
5
6
7
8
9
FileContent

version_id

storage_path

file_name

size

承認フロー

教師

1
2
3
4
5
保存

↓

DRAFT

申請

1
REVIEW

管理者

1
APPROVED

公開日時

1
PUBLISHED

公開終了

1
ARCHIVED

スケジュール公開

公開日時を持たせます。

1
2
3
publish_start

publish_end

例えば

1
2
3
4
5
6
7
4/1

公開開始

9/30

終了

これだけで

1
2
3
4
5
Spring Batch

または

Scheduler

で公開できます。


公開対象

将来のために

1
visibility

を追加します。

1
2
3
4
5
PUBLIC

PRIVATE

LIMITED

コースもVersion管理する?

ここが設計の分かれ目です。

私は

コースもVersion管理する

ことをおすすめします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Course

↓

CourseVersion

↓

Section

↓

Lesson

↓

ContentVersion

理由

1
2
3
4
5
6
7
8
9
Java入門

↓

章を追加

↓

公開中の教材は変えたくない

からです。


SectionもVersion管理する?

ここは不要です。

Sectionは

1
CourseVersion

に属せば十分です。

1
2
3
4
5
6
7
8
9
CourseVersion

↓

Section

↓

Lesson

で問題ありません。


LessonもVersion管理する?

これも不要です。

1
2
3
4
5
Lesson

↓

ContentVersion

だけで十分です。

Lessonは

1
2
3
タイトル

順番

程度なのでVersion化のメリットが少ないです。


将来追加できるもの

この設計なら

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
アンケート

ライブ配信

提出課題

AIチャット

コード実行

演習

PDF閲覧

音声教材

全部

1
ContentType

を増やすだけです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
VIDEO

TEXT

QUIZ

FILE

SURVEY

LIVE

ASSIGNMENT

AUDIO

CODE

AI_CHAT

この設計で追加できる運用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
ドラフト保存

承認

差し戻し

予約公開

公開終了

版管理

履歴比較

ロールバック

監査ログ

すべて対応できます。


ここでさらに改善したい点

ここまででかなり実運用に近い設計になりますが、企業向けLMSとして考えると、さらに次の3点を最初から取り入れることを強くおすすめします。

1. Lesson と Content の関係を見直す

現在は

1
2
3
Lesson
  ↓
Content

ですが、実際には「レッスン内で複数コンテンツを自由に並び替えたい」「同じコンテンツを別レッスンでも利用したい」という要件が出やすいです。

そのため、

1
2
3
4
5
Lesson
   ↓
LessonContent(中間テーブル)
   ↓
Content

とすると、コンテンツの再利用や並び順管理が柔軟になります。

2. 添付ファイルを共通化する

動画・テキスト・クイズそれぞれに画像やPDFを添付したくなることがあります。

1
2
3
ContentVersion
    ↓
Attachments

という共通添付テーブルを持たせると、サムネイルや補足資料を一元管理できます。

3. コンテンツタイプごとのテーブルをなくす選択肢

Spring Boot + JPAでは、ContentVersion を継承の親エンティティとし、VideoContentTextContentQuizContent などを継承エンティティとして扱う方法もあります。

ただし、JPA継承は複雑なJOINやMyBatisとの併用時に扱いが難しくなるため、このシステムでは現在提案している「共通テーブル + タイプ別詳細テーブル」の構成の方が保守しやすいと考えます。


次の第4段階

ここからはいよいよSpring Bootの実装を見据えた設計に入ります。

第4段階では、以下をまとめて設計します。

  • パッケージ構成

  • Entity一覧と継承関係

  • Repository / MyBatis Mapper の責務分担

  • DTO・Form・SearchCondition の設計

  • Service層の責務

  • Controller構成

  • Spring Security(認証・認可)設計

  • 画面一覧とURL設計

この段階まで設計すると、そのまま実装を開始できるレベルのアーキテクチャになります。


ご提案の以下2点については、改善をしてもう一度設計をお願いします。 1. Lesson と Content の関係を見直す 2. 添付ファイルを共通化する


この2点は、実際のLMS(Moodle、Canvas、Udemy Business、社内教育システムなど)でも重要になる部分です。

結論から言うと、私は以下のように変更することをおすすめします。

  • Lesson と Content は直接紐付けない

  • Content は教材ライブラリとして独立させる

  • LessonContent(中間テーブル)でレッスンへ配置する

  • 添付ファイルは Attachment として共通化する

これにより、コンテンツの再利用・バージョン管理・添付ファイル管理が非常にシンプルになります。


新しい教材構造

まず教材構造を変更します。

従来

1
2
3
4
5
6
7
Course
   │
Section
   │
Lesson
   │
Content

改善後

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Course
   │
Section
   │
Lesson
   │
LessonContent
   │
Content
   │
ContentVersion

ここで LessonContent を追加します。


なぜ LessonContent が必要なのか

例えば

1
2
3
4
5
6
7
Java基礎

Lesson1

・Javaとは
・環境構築
・HelloWorld

があります。

ところが

1
2
3
4
5
6
SpringBoot入門

Lesson0

・Javaとは
・環境構築

でも利用したくなる可能性があります。

現在の設計では

1
2
3
4
5
Lesson

↓

Content

なので

Javaとは

をコピーするしかありません。

コピーすると

1
2
3
4
5
6
7
Javaとは

コピー①

コピー②

コピー③

になります。

これは教材管理では非常によくある失敗例です。


中間テーブル方式

そこで

1
2
3
4
5
6
7
8
9
Lesson

↓

LessonContent

↓

Content

にします。

例えば

1
2
3
4
5
Content

ID=100

Javaとは

これを

1
2
3
4
5
Lesson1

↓

100
1
2
3
4
5
Lesson2

↓

100

のように配置できます。

つまり

1
2
3
4
5
Content

↓

教材ライブラリ

になります。

これはCMS(WordPress等)の記事と同じ考え方です。


LessonContent

新しいテーブル

1
lesson_contents
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
id

lesson_id

content_id

display_order

required_flag

visible_flag

created_at

required_flag

1
true

なら必須教材

false

なら参考資料

にもできます。


Content

Contentは教材そのものです。

1
2
3
4
5
6
7
8
9
id

content_type

author_id

current_version_id

created_at

Lessonとの関係は持ちません。


ContentVersion

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
id

content_id

version_no

title

summary

status

publish_start

publish_end

ここに本文があります。


コンテンツタイプ

1
2
3
4
5
6
7
VIDEO

TEXT

QUIZ

FILE

今まで通りです。


添付ファイルを共通化

ここはぜひ改善したいところです。

現在

1
2
3
Video

thumbnail
1
2
3
Text

image
1
2
3
Quiz

pdf

など

種類ごとに持とうとしていました。

しかし

実際には

テキスト教材にも

1
2
3
4
5
6
7
画像

PDF

Excel

ZIP

を添付したくなります。

動画にも

1
2
3
4
5
字幕

サムネイル

補足資料

を付けたくなります。

なので

Attachmentを共通化します。


Attachment

新テーブル

1
attachments
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
id

content_version_id

file_name

original_name

mime_type

extension

file_size

storage_type

storage_path

display_order

attachment_type

created_at

attachment_type

ここが重要です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
THUMBNAIL

MATERIAL

IMAGE

SUBTITLE

PDF

OTHER

例えば

動画なら

1
2
3
4
5
サムネイル

字幕

PDF

テキストなら

1
2
3
4
5
画像

PDF

ZIP

全部同じテーブルになります。


storage_type

1
2
3
4
5
6
7
8
9
LOCAL

R2

S3

YOUTUBE

VIMEO

Cloudflare R2へ移行しても

変更不要になります。


TextContent

かなりシンプルになります。

1
2
3
4
5
content_version_id

body

format

画像は

Attachment

になります。


VideoContent

1
2
3
4
5
6
7
8
9
content_version_id

video_type

video_id

video_url

duration

サムネイル

字幕

資料

Attachment

になります。


QuizContent

1
2
3
4
5
6
7
8
9
content_version_id

question

multiple_answer

answer_count

explanation

画像問題なら

Attachment

に画像を登録するだけです。


FileContent

ここだけ少し考え方が変わります。

以前

1
2
3
4
5
file_contents

↓

storage_path

でした。

でも

Attachmentがあるので

1
2
3
4
5
FileContent

↓

説明だけ

にします。

1
2
3
content_version_id

description

実際のファイルは

Attachment

になります。

つまり

Attachmentの

1
2
3
4
5
attachment_type

=

MATERIAL

になります。


新ER

これが私なら採用する構造です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
User
 │
 ├──────────────┐
 │              │
 ▼              ▼
Course      Content
 │              │
 ▼              ▼
Section    ContentVersion
 │              │
 ▼              ├──────────────┐
Lesson          │              │
 │              ▼              ▼
 ▼          VideoContent   TextContent
LessonContent   │              │
 │              ├──────────────┤
 ▼              ▼              ▼
Content     QuizContent   FileContent
                    │
                    ▼
               QuizChoice

ContentVersion
      │
      ▼
 Attachment

この設計のメリット

改善点 メリット
LessonContent追加 同じ教材を複数レッスンで再利用できる
Contentを独立 教材ライブラリとして管理できる
ContentVersion 公開版・下書きを共存できる
Attachment共通化 画像・PDF・字幕・ZIP・サムネイルを一元管理
FileContent簡素化 ファイル管理の重複がなくなる
Cloudflare R2対応 保存先を変更してもDB設計は変わらない
将来の拡張 音声・ライブ・AI教材なども追加しやすい

さらに実運用を見据えた改善案

このシステムを企業向け・商用レベルまで発展させるなら、Content(教材ライブラリ)と Course(コース)を完全に分離することをおすすめします。

つまり、教師は最初に「教材ライブラリ」でコンテンツを作成・承認し、その後コースのレッスンへドラッグ&ドロップのような感覚で配置する構成です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
教材ライブラリ
    │
    ├─ Javaとは
    ├─ Javaの歴史
    ├─ Hello World
    ├─ クイズ①
    └─ PDF資料
          │
          ▼
      LessonContent
          │
          ▼
        Lesson
          │
          ▼
       Section
          │
          ▼
        Course

この構成であれば、

  • 同じ教材を複数コースで再利用できる

  • 教材を更新すると、どのコースで利用されているか追跡できる

  • 「教材ライブラリ」と「コース設計」が分離され、運用しやすい

  • 将来的に教材の販売や共有、教材テンプレート機能なども追加しやすい

という大きな利点があります。

この設計は、これまでのご要望(JPA + MyBatis + Spring Security + Thymeleaf)とも非常に相性が良く、長期運用を前提とした教材管理システムとしておすすめできる構成です。


ありがとうございます。「教材ライブラリ」の設計を採用してください。


ありがとうございます。
私もこのシステムであれば、「教材ライブラリ」方式が最適だと思います。

ここまでのお話を踏まえると、このシステムは「教材管理システム」ではなく、LMS(Learning Management System)+ LCMS(Learning Content Management System) に近い構成になります。

  • LCMS:教材(コンテンツ)を作成・管理・承認・バージョン管理する

  • LMS:教材を組み合わせてコースを作成し、受講・進捗管理を行う

この2つを最初から分離しておくと、将来的な拡張性が非常に高くなります。


最終アーキテクチャ

大きく4つのドメインに分けます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
┌─────────────────────────────┐
│      コンテンツ管理(LCMS)      │
├─────────────────────────────┤
│ Content Library             │
│ Content                     │
│ ContentVersion              │
│ VideoContent                │
│ TextContent                 │
│ QuizContent                 │
│ FileContent                 │
│ Attachment                  │
└─────────────────────────────┘

               ↓ 利用

┌─────────────────────────────┐
│      コース管理(LMS)          │
├─────────────────────────────┤
│ Course                      │
│ CourseVersion               │
│ Section                     │
│ Lesson                      │
│ LessonContent               │
└─────────────────────────────┘

               ↓ 公開

┌─────────────────────────────┐
│       学習管理                 │
├─────────────────────────────┤
│ Enrollment                  │
│ LearningHistory             │
│ QuizAnswer                  │
│ Progress                    │
└─────────────────────────────┘

               ↓

┌─────────────────────────────┐
│       管理機能                 │
├─────────────────────────────┤
│ Approval                    │
│ Notification                │
│ AuditLog                    │
└─────────────────────────────┘

コンテンツライブラリ

ここが教師の作業場所になります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
教材ライブラリ

├ Javaとは
├ Java変数
├ Java配列
├ SpringDI
├ SpringAOP
├ クイズ①
├ PDF資料
└ 動画①

教師はここで教材を作成します。

まだコースには所属しません。


コース作成

教材を組み合わせます。

例えば

1
Java入門

を作るとします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Java入門

├ 第1章
│
│   ├ Javaとは
│   ├ Java変数
│   └ クイズ①
│
├ 第2章
│
│   ├ Java配列
│   └ PDF資料
│
└ 第3章
    └ SpringDI

この「配置」だけを管理します。

教材自体はコピーしません。


LessonContent

ここが重要になります。

1
2
3
4
5
6
7
8
9
Lesson

↓

LessonContent

↓

Content

例えば

1
2
3
Lesson

Java基礎

には

1
2
3
4
5
Javaとは

変数

配列

を配置。

別の

1
SpringBoot研修

では

1
2
3
Javaとは

変数

だけ利用できます。


Content

Contentは教材そのものです。

1
2
3
4
5
6
7
8
9
Content

id

type

author

current_version

ここには

本文はありません。


ContentVersion

ここに教材があります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
ContentVersion

id

content_id

version

status

title

summary

publish_start

publish_end

詳細テーブル

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
ContentVersion

│

├ VideoContent

├ TextContent

├ QuizContent

└ FileContent

この考えは変えません。


Attachment

全コンテンツ共通になります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Attachment

id

content_version_id

attachment_type

storage_path

mime_type

display_order

例えば

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Javaとは

↓

画像①

画像②

PDF

ZIP

全部ここです。


コースバージョン

ここは以前より強くおすすめします。

1
2
3
4
5
Course

↓

CourseVersion

理由

公開中コース

1
Java入門

1
第4章

を追加したい。

でも

受講中の人は

旧版

を見せたい。

なので

1
2
3
4
5
Course

↓

Version

は必須になります。


教材配置

最終的には

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
CourseVersion

↓

Section

↓

Lesson

↓

LessonContent

↓

Content

↓

ContentVersion

になります。

ここが最終形です。


Section

Sectionは無限階層です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Java

├ 基礎

│   ├ 文法

│   │   └ Lesson

│   └ クラス

└ Spring

    └ DI

        └ Lesson

parent_section_id

だけで実現します。


公開フロー

教材

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
教師

↓

教材ライブラリ

↓

下書き

↓

申請

↓

承認

↓

公開

コース

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
教師

↓

コース編集

↓

申請

↓

承認

↓

公開

教材とコースを分離します。


受講者

受講者は

1
公開CourseVersion

しか見えません。

中では

1
2
3
4
5
LessonContent

↓

ContentVersion

公開版だけ

取得します。


将来追加予定の機能

この設計なら

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
教材販売

教材共有

教材テンプレート

教材コピー

AI生成教材

教材タグ

教材検索

教材レビュー

教材お気に入り

教材利用ランキング

全部追加できます。


私が追加したい設計

ここからは、このシステムを企業向け・商用レベルにするために、ぜひ最初から取り入れたい設計です。

① ContentTag(教材タグ)

カテゴリとは別にタグを持たせます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Content

↓

ContentTag

↓

Java
Spring
入門
動画
初心者

ライブラリ検索が圧倒的に使いやすくなります。


② ContentRelation(関連教材)

教材同士を関連付けます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Javaとは

↓

関連教材

・変数

・クラス

・オブジェクト指向

受講画面にも表示できます。


③ ContentUsage(教材利用状況)

これを追加すると

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Javaとは

↓

利用中

Java入門

SpringBoot

新人研修

がすぐ分かります。

これは非常に重要です。

教材を修正するとき

1
2
3
4
5
6
7
この教材を変更すると

8コース

132レッスン

で使われています

と表示できます。


次の設計で追加したいもの

ここまでくると、Spring Boot実装前に「ドメインモデル」を確定させたい段階です。

次は単なるテーブル一覧ではなく、

  • Entityクラス一覧

  • Entity同士の関連(@OneToMany@ManyToOne など)

  • JPAで管理するEntity

  • MyBatisで取得するDTO

  • RepositoryとMapperの責務

  • Service間の依存関係

まで含めた実装レベルのアーキテクチャ設計を行うことをおすすめします。

ここまで設計すれば、そのままSpring Boot 3 + JPA + MyBatis + Spring Securityで実装を開始できるレベルになります。