---
title: "複数の音楽グループ対応 データ設計書"
---

最終更新: 2026-11-09
関連 Issue: MorningStatusApp#1803（親Issue）/ morning-status-blume#245（初版作成）/ MorningStatusApp#1808（実装）/ MorningStatusApp#1813（モーニング娘。の移行）

## 1. 概要

ハロー!プロジェクトの複数の音楽グループを扱うためのデータ設計。モーニング娘。を主とし、他の音楽グループの情報を、モーニング娘。の情報から紐づけて参照できるようにする。

参照できる情報は、次の4パターンである（画面設計 → morning-status-blume#246）。

- イベントで共演したメンバー情報
- ラジオでオンエアした楽曲情報
- プレイリストに掲載した楽曲情報
- 音楽グループを横断したユニット情報

最終形は、ハロー!プロジェクトの全音楽グループを切り替えて参照できることである。どの音楽グループも、モーニング娘。と同じ情報量を持てる構造にする。最初の実装では、この構造の一部だけを使う。

### 音楽グループとユニット

| 単位 | 意味 | 例 |
| --- | --- | --- |
| 音楽グループ（`MusicGroup`） | メンバーの本拠地。メンバーファイルの単位 | モーニング娘。 |
| ユニット（`Unit`） | 音楽グループの内で、または音楽グループをまたいで結成される集団 | ミニモニ。、ConChu!、DECA!BEAT |
| 広義のユニット | リリース・収録曲に紐づく単位。ユニットまたは音楽グループのいずれか | 上の例のすべて |

リリース・収録曲（`Track.unitId` 等）に紐づくのは、広義のユニットである。広義のユニットの `id` は、`units.json`（ユニット）または `groups.json`（音楽グループ）の `id` と対応する。ユニットが、どの音楽グループのものかを持つ必要はない。

---

## 2. 背景・目的

| 目的 | 詳細 |
| --- | --- |
| 他の音楽グループの登録 | 音楽グループを特定せず、どの音楽グループでも登録できる構造にする |
| モーニング娘。との紐づけ | 共演メンバー・オンエア曲・プレイリストの曲・混成ユニットを通じて、モーニング娘。の情報から他の音楽グループの情報へ辿れるようにする |
| メンバーの記録の一元的な引き方 | 同じ人が複数の音楽グループに載る場合も、メンバーID（`id`）から、載っている音楽グループと、その音楽グループでの記録を引けるようにする |
| リリースとの紐づけ | 音楽グループを、ユニットと並ぶ、リリース・収録曲と紐づく単位として扱う |

### 対象範囲

- **対象**: 音楽グループの定義、音楽グループごとのメンバーファイル、メンバーの引き方、ユニット・リリースとの関係、SNSアカウントの登録
- **対象外**: §10 を参照

---

## 3. 型定義

### 音楽グループ（`MusicGroup`）（`types/music-group.ts`）

型は Zod スキーマ（`MusicGroupSchema`）から `z.infer` で導出する。

```typescript
export const MusicGroupSchema = z.object({
  /** 音楽グループの識別子（kebab-case。例: morning-musume）。units.json の id と重ならない */
  id: z.string().min(1),
  /** 音楽グループ名 */
  name: z.string().min(1),
  /** メンバーファイルの Blob 相対パス */
  membersPath: z.string().min(1),
  /** 活動開始年（YYYY 形式） */
  activeFrom: z.string(),
  /** 活動終了年（YYYY 形式。活動中の場合は省略） */
  activeTo: z.string().optional(),
  /** MusicBrainz のアーティスト ID（MBID）。設定すると sync-discography のリリース取得対象になる */
  mbid: z.string().optional(),
  /** 関連リリースの ID 一覧（Release の id。sync-discography が自動集約して書き戻す） */
  releaseIds: z.array(z.string()).optional(),
});
export type MusicGroup = z.infer<typeof MusicGroupSchema>;
```

メンバーは、既存の `Member`（[メンバー データ設計書](/design/common/member-data-design) §3）をそのまま使う。音楽グループごとに別の型は持たない。

---

## 4. データファイル

### groups.json（Vercel Blob）

- 音楽グループの一覧（`MusicGroup[]`）を管理する。Vercel Blob で管理し、相対パス `groups.json` で指定する（[データ設計書（全体概要）](/design/common/data-design) §5）
- モーニング娘。も、他の音楽グループと同じく1件として登録する。`membersPath` は `members.json` とする。モーニング娘。は音楽グループとして `groups.json` にだけ登録し、MBID は `groups.json` の `morning-musume` の `mbid` に持つ
- 他の音楽グループの `membersPath` は、`groups/{id}/members.json` とする。`groups/` を付けるのは、`instagram/`・`ameba/`・`daily-digest/` 等の既存のフォルダと名前が重ならないようにするため
- `releaseIds` は `sync-discography` が自動集約して書き戻す（§8）
- 並び順を、画面での音楽グループの表示順とする

### members.json と `groups/{id}/members.json`（Vercel Blob）

- モーニング娘。のメンバーは、現在と同じ `members.json` に置く
- 他の音楽グループのメンバーは、その音楽グループの `membersPath` のファイルに置く
- どのファイルも、形式は `{ members: Member[] }` で、メンバーの項目は [メンバー データ設計書](/design/common/member-data-design) §3 と同じである
- メンバーが載っているファイルが、そのメンバーの本拠地の音楽グループを決める。`Member` に、音楽グループの項目は持たせない
- メンバーの `id` は氏名に由来する。同じ人は、どの音楽グループのファイルでも同じ `id` で載る

### サンプルデータ（groups.json）

```json
[
  {
    "id": "morning-musume",
    "name": "モーニング娘。",
    "membersPath": "members.json",
    "activeFrom": "1997",
    "mbid": "00000000-0000-0000-0000-000000000000",
    "releaseIds": ["release-id-1", "release-id-2"]
  },
  {
    "id": "group-id-1",
    "name": "音楽グループ名",
    "membersPath": "groups/group-id-1/members.json",
    "activeFrom": "1999",
    "mbid": "11111111-1111-1111-1111-111111111111",
    "releaseIds": ["release-id-3"]
  }
]
```

---

## 5. フィールド詳細

| フィールド | 型 | 必須 | 説明 |
| --- | --- | --- | --- |
| `id` | `string` | ✅ | 音楽グループの識別子（kebab-case）。`units.json` の `id` と重ならない |
| `name` | `string` | ✅ | 音楽グループ名 |
| `membersPath` | `string` | ✅ | メンバーファイルの Blob 相対パス。モーニング娘。は `members.json`、他の音楽グループは `groups/{id}/members.json` |
| `activeFrom` | `string` | ✅ | 活動開始年（YYYY 形式） |
| `activeTo` | `string` | — | 活動終了年（YYYY 形式）。活動中の音楽グループは省略 |
| `mbid` | `string` | — | MusicBrainz のアーティスト ID（MBID）。`sync-discography` がリリースを取得する対象と、トラックとの照合（§8-2）に使う |
| `releaseIds` | `string[]` | — | 関連リリースの ID 一覧（`Release` の `id` と一致）。`sync-discography` が自動集約する |

### 整合性ルール

| ルール | 内容 |
| --- | --- |
| 広義のユニットの `id` の一意性 | `groups.json` の `id` は重複せず、`units.json` の `id` とも重ならない |
| モーニング娘。の登録 | `id` が `morning-musume` の音楽グループが1件あり、`membersPath` は `members.json` であること |
| 年の形式 | `activeFrom`・`activeTo` は `YYYY` 形式で、`activeTo` は `activeFrom` 以降であること |
| ファイル内のメンバーID | 1つのメンバーファイルの中で、`id` は重複しない |
| 複数のファイルの同じメンバーID | 複数のメンバーファイルに同じ `id` がある場合、同じ人であること。氏名（`name`）と生年月日（`profile.dob`）が一致する |
| ユニットのメンバーの実在 | `Unit.memberIds` の各 `id` は、いずれかの音楽グループのメンバーファイルに存在すること |

整合性ルールの検証は、Blob ファイル診断バッチ（[バッチ設計書](/design/common/batch-design) W16）の対象とする。

`groups.json` の読み込みは、`units.json` と同じ方式とする。配列として読み込めた内容を、そのまま音楽グループの一覧として使う。取得に失敗した場合と、配列でない場合は、音楽グループを0件として扱う。

---

## 6. メンバーの引き方

### 6-1. メンバー索引

メンバーの `id` からそのメンバーが載っている音楽グループを引く索引を実行時に導出する。永続ファイルは持たない。

- `groups.json` の各 `membersPath` のファイルを読み、`id` ごとに、載っている音楽グループの `id` を `groups.json` の並び順でまとめる
- 同じ `id` が複数のファイルに載る場合（移籍・兼任したメンバー等）、その `id` は複数の音楽グループの `id` を持つ
- 読み込みの結果は、`members.json` と同じ再検証間隔（3600秒）でキャッシュする。キャッシュのタグには、各ファイルの相対パスを付ける
- メンバーファイルの読み込みに失敗した場合（未作成・取得失敗・形式不正）は、その音楽グループのメンバーを空として扱う。他の音楽グループの読み込みには影響しない
- 索引は、メンバーファイルのみを情報源とする。メンバーを追加・削除したときに、索引のための項目を別に直す必要はない

### 6-2. 記録の選び方

同じ人が複数の音楽グループに載るとき、各ファイルのエントリは、その音楽グループでの記録（メンバーカラー・期・在籍状況等）である。どの記録を使うかは、引く場面で決まる。

| 引く場面 | 使う記録 |
| --- | --- |
| 音楽グループの画面（その音楽グループを表示する画面） | その音楽グループの記録 |
| 上記以外（ユニットの `memberIds` から引く場合を含む） | モーニング娘。の記録。載っていなければ、`groups.json` の並び順で最初の音楽グループの記録 |

引く場面ごとの記録の選び方は、共通の関数1つで行い、画面ごとに複製しない。

### 6-3. メンバー詳細での扱い

同じ `id` のメンバーが複数の音楽グループに載るとき、メンバー詳細（`/members/{id}`）で記録をどう見せるかは、画面設計書（morning-status-blume#246）で定める。

---

## 7. ユニットとの関係

- `units.json` は1ファイルとし、すべてのユニットを同じファイルに登録する。ユニットは、どの音楽グループのものかを項目として持たない（[ユニット一覧機能 データ設計書](/design/unit/unit-data-design)）
- ユニットの `parentId` は、`units.json` の `id`、または `groups.json` の `id`（音楽グループ）を指す。モーニング娘。の西暦年を含む名称のユニット（「モーニング娘。'14」〜「モーニング娘。'26」等。このアプリで、リリースの名義に対応づけるためにユニットとして定義したもの）は、`parentId` に音楽グループ `morning-musume` を指す
- ユニットの `memberIds` には、どの音楽グループのメンバーの `id` も入れられる。音楽グループをまたいで結成されるユニット（DECA!BEAT、ConChu! 等）の所属メンバーは、複数の音楽グループのメンバーで構成できる。`id` から記録を引く方法は §6 に従う

---

## 8. リリースとの関係

- リリース（`Release`）・収録曲（`Track`）は、Neon の既存のテーブルを共用する。音楽グループごとのテーブルは持たない
- リリースに、音楽グループを表す項目は足さない
- 収録曲の `Track.unitId` は、広義のユニットの `id` で、`units.json`（ユニット）または `groups.json`（音楽グループ）の `id` と対応する

リリースは、収録曲を通じて、広義のユニット（ユニット・音楽グループ）とソロアーティストに紐づく。1つのリリースに複数が紐づくことがある（[リリース データ設計書](/design/common/release-data-design) §4-5）。

| 紐づく相手 | 紐づけの持ち方 |
| --- | --- |
| ユニット | `Unit.releaseIds` |
| 音楽グループ | `MusicGroup.releaseIds` |
| ソロアーティスト | `SoloArtist.releaseIds` |

### 8-1. リリース取得対象の MBID

ユニットの `mbid` とソロアーティストの `mbids` に加えて、`groups.json` の `mbid` も、`sync-discography`（[バッチ設計書](/design/common/batch-design) W2）のリリース取得対象に含める。重複排除は、合算後の一覧に対して行う。

### 8-2. 収録曲との照合と `releaseIds` の自動集約

- 収録曲の主体（[トラックリスト データ設計書](/design/discography/tracklist-data-design) §4「名義の解釈」）は、`units.json` のユニットと `groups.json` の音楽グループの両方に対して、ユニットと同じ照合（[ユニット一覧機能 データ設計書](/design/unit/unit-data-design) §6）で照合し、一致した広義のユニットの `id` を `Track.unitId` にする
- `Track.unitId` が音楽グループの `id` であるトラックを1曲以上含むリリースを、その音楽グループの `releaseIds` に自動追加する。既存の `releaseIds` は保持し、重複は除去する
- 1つのリリースに複数の広義のユニットのトラックが含まれる場合は、それぞれの `releaseIds` に追加する
- 自動集約の後、`groups.json` を Vercel Blob に書き戻す

### 8-3. `Track` 型は変更しない

収録曲の項目は増やさない。`Track.unitId` の対応先が、`units.json` と `groups.json` の2つになる。

### 8-4. モーニング娘。のリリース

「モーニング娘。のリリース」の判定（[リリース データ設計書](/design/common/release-data-design) §4-5）は、共通の関数1つで行う。モーニング娘。のユニットは、`id` が `morning-musume` の音楽グループと、`parentId` が `morning-musume` のユニットである。モーニング娘。のリリースを使う画面・機能は、すべてこの判定を通して選ぶ。

実装時に、モーニング娘。のリリースを使う画面・機能のうち、この判定を通さずにリリースを全件扱っている箇所がないかを確認する。

---

## 9. SNS

- 他の音楽グループのメンバーのSNSアカウントは、そのメンバーのエントリ（`officialSns`・`youtubeChannel`・`tiktokChannel`）に登録する。項目は [メンバー データ設計書](/design/common/member-data-design) §3 と同じである
- SNSの投稿の収集と近況の自動検索は、モーニング娘。の `members.json` のメンバーを対象とする。他の音楽グループのメンバーは、アカウントの登録だけを行う
- 投稿データ（`TikTokPost`・`YoutubePost`）の項目は増えない
- 公式アカウント一覧（`tiktok-official-accounts.json`・`instagram-official-accounts.json`）は、モーニング娘。の公式アカウントを収集するための設定である

---

## 10. 対象外

| 項目 | 内容 |
| --- | --- |
| 音楽グループの切り替え | 音楽グループを切り替えて参照する機能の実装（置き場所の方針は画面設計書 morning-status-blume#246 で定める） |
| 他の音楽グループのSNS収集 | 投稿の収集、同期状態と新着ウィンドウの音楽グループ単位での管理、新着表示の音楽グループでの絞り込み、他の音楽グループの公式アカウントの登録（SNS収集を始める段階で検討する） |
| 他の音楽グループのシード | 他の音楽グループのメンバー・SNSアカウント・ユニット・`groups.json` の登録データの作成（別途行う） |
| メンバーの編集画面 | 他の音楽グループのメンバーの編集（`PUT /api/members` の対象は、モーニング娘。の `members.json` のみ） |
| 出身地照合用メンバー | Neon の出身地照合用メンバー（`Member`、[メンバー データ設計書](/design/common/member-data-design) §6）の他の音楽グループへの対応 |

---

## 11. データ取得層の設計方針

Server Component での直接取得とするか、Route Handler（BFF）+ TanStack Query とするかの判定は、画面が決まる画面設計書（morning-status-blume#246）の作成後に行い、結果を[統合フィードアイテム データ設計書](/design/common/feed-data-design)の実績ログに追記する（判定の基準は[データ設計書（全体概要）](/design/common/data-design) §4）。

---

## 12. 関連設計書

| 設計書 | 本書との関係 |
| --- | --- |
| [メンバー データ設計書](/design/common/member-data-design) | 音楽グループごとのメンバーファイルが使う `Member` の定義 |
| [ユニット一覧機能 データ設計書](/design/unit/unit-data-design) | ユニット（`Unit`）の定義と、収録曲との照合・`releaseIds` の自動集約 |
| [トラックリスト データ設計書](/design/discography/tracklist-data-design) | `Track.unitId` の対応先と、収録曲の主体 |
| [リリース データ設計書](/design/common/release-data-design) | リリースと、広義のユニット・ソロアーティストの紐づけ、モーニング娘。のリリースの判定 |
| [データ設計書（全体概要）](/design/common/data-design) | データファイル一覧・エンティティ一覧への登録、Blob ファイルの指定方式 |
| [バッチ設計書](/design/common/batch-design) | `sync-discography`・Blob ファイル診断バッチの処理 |

---

## 13. モーニング娘。の移行（`units.json` → `groups.json`）

`units.json` の `morning-musume`（ユニット）を、音楽グループとして `groups.json` に移す。一回限りのパッチ `scripts/patch/migrate-morning-musume-to-groups.ts` で行う（[バッチ設計書](/design/common/batch-design) 一回限りパッチ一覧）。

### 13-1. 移す内容

`units.json` の `morning-musume` から、`groups.json` の1件を次のとおり作る。

| `groups.json` の項目 | 元 |
| --- | --- |
| `id` | `morning-musume` の `id` |
| `name` | `morning-musume` の `name` |
| `membersPath` | `members.json`（固定） |
| `activeFrom` | `morning-musume` の `activeFrom` |
| `activeTo` | `morning-musume` の `activeTo`（あれば） |
| `mbid` | `morning-musume` の `mbid` |
| `releaseIds` | `morning-musume` の `releaseIds` |

- 移す項目は、上の表の7項目である。ドライランは、`morning-musume` が持つ、この7項目以外の値（`creditMbids` 等）を表示する
- `parentId` が `morning-musume` のユニット（西暦年を含む名称のユニット、派生ユニット）は変更しない。`parentId` は、移行後は `groups.json` の `id`（音楽グループ）を指す（§7）

### 13-2. 処理の順序

1. `units.json` と `groups.json` を、キャッシュを通さずに読む。読み込みに失敗した場合と、配列でない場合は、何も書かずに中止する。`units.json` が見つからない場合も、中止する。`groups.json` がまだない場合は、空として扱う（最初の移行では、`groups.json` は未作成である）
2. 書き込み後の `groups.json` と `units.json` を組み立て、`id` が重ならないことを検証する。重なる場合は、何も書かずに中止する
3. 実行前の `units.json` の控えを `data/work/` に、実行日時を付けたファイル名で出力し、出力先のパスを表示する。控えの出力先の確認は、書き込みより前に行う。確認の内容は、同名のファイルがないことと、出力先のディレクトリを使えることで、ドライランでも行う（本実行は、`data/work/` がなければ作る。ドライランは、`data/work/` がなければ作らず、既存の親ディレクトリに書き込めることを確認する）
4. `groups.json` の末尾に `morning-musume` を追加する。`groups.json` の並び順は、画面での音楽グループの表示順である（§4）。13-3 の順序では、他の音楽グループの登録より前に本パッチを実行するため、`morning-musume` が先頭の1件になる
5. `units.json` から `morning-musume` を外す
6. 本番のキャッシュを破棄する

- 手順2で、`groups.json` に同一内容の `morning-musume` がある場合は、手順4を省き、手順5から続ける。内容が異なる場合は、上書きせずに中止する。途中で止まった場合は、同じコマンドを再実行する
- `units.json` に `morning-musume` がなく、`groups.json` に `morning-musume` がある場合は、移行済みとして、書き込みは行わず、キャッシュの破棄だけを行う（キャッシュの破棄だけが失敗した後の再実行で、破棄をやり直すため）
- ドライランは、作る内容（`releaseIds` は件数）と外す内容を表示し、書き込みとキャッシュの破棄は行わない。移行済みの場合も、何も行わない

### 13-3. 本番データの切替順序

コードを、移行前後のどちらのデータでも既存のモーニング娘。の表示・動作が変わらない状態で、先にデプロイする。そのうえで、本番のデータを次の順序で切り替える。

| 手順 | 内容 |
| --- | --- |
| 1・2 | 本パッチを実行する（`groups.json` の作成と、`units.json` からの除去を続けて行う） |
| 3 | W2 の更新（`groups.json` の `releaseIds` の集約）をデプロイしてから、W2 を手動実行する。W16 の `groups.json` の検証は、`groups.json` の作成後に有効にする |
| 4 | 他の音楽グループのメンバーを投入してから、ユニットの `memberIds` を追加する |
| 5 | リリースを取り込む |

W2 は手動実行のみのため、本パッチの実行から、手順3の W2 の更新のデプロイまでの間は、手動実行しない（`units.json` から `morning-musume` が外れた状態で、更新前の W2 を実行しないため）。各手順の前にドライランで確認し、戻し方として、実行前の `units.json` の控え（13-2 の手順2）を用意する。

---

## 改訂履歴

| 版 | 更新日 | 変更内容 |
| --- | --- | --- |
| 1.3 | 2026-11-09 | 【実装時に追記（MorningStatusApp#1813）】§13-2 に、移行済みの場合はキャッシュの破棄だけを行うこと（キャッシュの破棄だけが失敗した後の再実行のため）、ドライランは `data/work/` を作らずに書き込めるかを確認すること、ドライランは `releaseIds` を件数で表示することを追記（MorningStatusApp#1825 のレビュー指摘） |
| 1.2 | 2026-11-08 | 【実装時に追記（MorningStatusApp#1813）】§13 モーニング娘。の移行（`units.json` → `groups.json`）を追加（MorningStatusApp#1813） |
| 1.1 | 2026-11-08 | 【実装時に追記（MorningStatusApp#1812）】`groups.json` の読み込み方式を、`units.json` と同じ（配列として読み込めた内容をそのまま使う）と明記。§5 の見出し「`MusicGroup`」を削除（ソロアーティスト データ設計書の構成に合わせた）。§6-1 に、メンバーファイルの読み込みに失敗した場合の扱いを追記 |
| 1.0 | 2026-11-07 | 【実装時に追記（MorningStatusApp#1808）】初版作成（MorningStatusApp#1803, morning-status-blume#245） |
