- ドキュメント整理して、実装方針をもう一度立てたほうが良さそう
### テーブルの設計方針について
- テーブル名
- assignments → challengesに変える
- task_status_histories → task_status_change_logs、user_status_change_logsに変える?
- イベントソーシングとUPDATE許容のハイブリッド、どこまで履歴とるかがややこしくなるのがよくない
- 変更履歴はあとからとれるようにできる設計にはしておくぐらいに留めよう
- イベントソーシングは大変
- update前提でいったん構築したほうが良いかも
- logテーブルは後からでも追加できる
テーブル設計を変えてみた
before:
><img src="../../../image/2025-09-14-06-31-30.png" width="500">
after:
><img src="../../../image/2025-09-14-06-32-08.png" width="600">
[dbdiagram](https://dbdiagram.io/d/68c5dbb2841b2935a66d64a6)
```sql
//// ------------------------------------------------------
//// Users
//// ------------------------------------------------------
// ユーザー情報
Table users {
id int [pk, increment]
user_status_id int [not null, ref: > user_status.id]
user_type_id int [not null, ref: > user_types.id]
email_address varchar(255) [unique, not null]
first_name varchar(255) [not null]
last_name varchar(255) [not null]
created_at timestamp [default: `now()`, not null]
updated_at timestamp [default: `now()`, not null]
}
// ユーザーの在籍ステータスのマスターデータ
Table user_status {
id int [pk, increment]
name varchar(50) [unique, not null, note: '例: 在籍, 休会, 退会']
created_at timestamp [default: `now()`, not null]
updated_at timestamp [default: `now()`, not null]
}
// ユーザー種別のマスターデータ
Table user_types {
id int [pk, increment]
name varchar(50) [unique, not null, note: '例: student, admin']
created_at timestamp [default: `now()`, not null]
updated_at timestamp [default: `now()`, not null]
}
//// ------------------------------------------------------
//// Teams
//// ------------------------------------------------------
// チーム情報
Table teams {
id int [pk, increment]
name varchar(255) [unique, not null]
created_at timestamp [default: `now()`, not null]
updated_at timestamp [default: `now()`, not null]
}
// チームのメンバー所属履歴
Table team_membership_histories {
id int [pk, increment]
user_id int [not null, ref: > users.id]
team_id int [not null, ref: > teams.id]
start_date date [not null]
end_date date [null, note: 'NULLの場合は現在も所属中']
created_at timestamp [default: `now()`, not null]
updated_at timestamp [default: `now()`, not null]
}
//// ------------------------------------------------------
//// Challenges & UserTasks
//// ------------------------------------------------------
// 課題のマスターデータ
Table challenges {
id int [pk, increment]
challenge_category_id int [not null, ref: > challenge_categories.id]
name varchar(255) [unique, not null]
content_url varchar(2048) [unique, not null]
created_at timestamp [default: `now()`, not null]
updated_at timestamp [default: `now()`, not null]
}
// 課題カテゴリのマスターデータ
Table challenge_categories {
id int [pk, increment]
name varchar(255) [unique, not null]
created_at timestamp [default: `now()`, not null]
updated_at timestamp [default: `now()`, not null]
}
// 課題進捗ステータスのマスターデータ
Table task_status {
id int [pk, increment]
name varchar(50) [unique, not null, note: '例: 未着手, 取組中, レビュー待ち, 完了']
created_at timestamp [default: `now()`, not null]
updated_at timestamp [default: `now()`, not null]
}
// ユーザーごとの課題進捗
Table user_tasks {
id int [pk, increment]
user_id int [not null, ref: > users.id]
challenge_id int [not null, ref: > challenges.id]
task_status_id int [not null, ref: > task_status.id]
created_at timestamp [default: `now()`, not null]
updated_at timestamp [default: `now()`, not null]
Indexes {
(user_id, challenge_id) [unique]
}
}
```
変更点:
- user_typeマスタを追加
- challenge_categoriesマスタを追加
- challengeは、スクールが設定した課題マスタという意味にrename
- user_tasksは、userのchallengeの進捗状況という形でrename
- team_membershipテーブルはteam_membership_historiesにrename
- ユーザの在籍状況、ユーザの課題の進捗はイベントソーシング指向をやめて、最新時点のupdateを残すテーブルにする
- ログを残してほしいと言われたときは、新しくログテーブルを用意して蓄積するように変更する
AIに相談して設計として問題なさそうだったので、今後起こると困りそうな要件変更を聞いてみた。
1. あるユーザーが「メンター」でありながら「管理者」のような、複数の役割を兼務する必要が出てきた。
2. ある課題の内容が古くなったため、コンテンツを大幅に更新した。しかし、更新前にその課題に取り組んでいたユーザーには古いバージョンのまま進めてもらい、新規ユーザーには新しいバージョンに取り組んでもらいたい。
3. 課題のステータスが「レビュー待ち」になった後、誰がレビューし、どのようなコメントを残して「完了」または「取組中(差し戻し)」にしたのか、その詳細な履歴を記録したくなった。
2, 3あたりはありえそうなので、仮に要件変更が起きてマイグレーションするならどんな運用フローで進めますか?と聞いてみた。
- nullカラムを追加して、アプリ側はnullカラムに書き込むようにする
- [[Expand and Contract]]パターン
- challenge履歴テーブルと最新challengeテーブルを新しく作る
- アプリ側は今までのテーブルと、新しく作ったテーブルに二重書き込みする
- データの整合が取れることを確認したら、新しいテーブルにのみ参照するように切り替える
- 古いテーブルを削除する
Expand and Contractパターンも出してきて、模範解答じゃん・・・となった。