From 32c56008999f18078771d1cc9af8008b54059a16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=BF=E3=81=A6=E3=82=8B=E3=81=9E?= Date: Tue, 10 Mar 2026 23:45:37 +0900 Subject: [PATCH] --- ...%8C%E6%96%B9%E9%87%9D%E8%8D%89%E6%A1%88.md | 1099 +++++++++++++++++ 1 file changed, 1099 insertions(+) create mode 100644 %E7%A7%BB%E8%A1%8C%E6%96%B9%E9%87%9D%E8%8D%89%E6%A1%88.md diff --git a/%E7%A7%BB%E8%A1%8C%E6%96%B9%E9%87%9D%E8%8D%89%E6%A1%88.md b/%E7%A7%BB%E8%A1%8C%E6%96%B9%E9%87%9D%E8%8D%89%E6%A1%88.md new file mode 100644 index 0000000..16c132f --- /dev/null +++ b/%E7%A7%BB%E8%A1%8C%E6%96%B9%E9%87%9D%E8%8D%89%E6%A1%88.md @@ -0,0 +1,1099 @@ +# BTRC Hub バックエンド v2 移行仕様書草案 + +- 作成日: 2026-03-10 +- 対象: BTRC Hub backend v1 → v2 +- 位置づけ: 設計見直しのための草案 +- 前提: 2026-03-08 時点の現行仕様書、ソースコード、Wiki、Issue スナップショットを踏まへた再設計案 + +--- + +## 1. 本書の目的 + +本書は、現行の BTRC Hub backend v1 を全面否定するための文書ではない。 +むしろ、v1 で既に形になってゐる強い核を残しつつ、今後の拡張で説明コストと実装コストが膨らみやすい箇所を切り分け、**v2 でどの境界を引き直すべきか** を明文化するための移行仕様書である。 + +本書が扱ふのは主に次の 3 点である。 + +1. v1 のどこが構造的な弱点になってゐるか +2. v2 ではどのやぅなドメイン境界・テーブル境界を採るか +3. v1 から v2 へどう段階移行するか + +本書は「今すぐ全部作り直す」ことを強制するものではない。 +ただし、今後も BTRC Hub を育てるのであれば、**一度きちんと切り分けておかないと将来の足回りが重くなる** といふ認識に立ってゐる。 + +--- + +## 2. v1 の評価 + +先に結論を書く。 + +v1 は失敗作ではない。むしろ、個人開発としてはかなり芯がある。 +特に以下は v1 の明確な長所であり、v2 でも残すべきである。 + +- 投稿・タグ・Wiki の三本柱が明確 +- 単なるリンク集ではなく、知識蓄積基盤として設計されてゐる +- タグ別名、上位タグ、Wiki 履歴、外部同期、類似度といった中核機能が既に存在する +- 外部ソースから内部知識へ変換する ETL 的発想がある +- 監査性や履歴を重視する思想がある + +一方で、v1 には「壊れてゐる」といふより、**賢く作らうとして責務が寄りすぎた部分** がある。 +それが今後の弱点になる。 + +本草案では、その弱点を次の 6 類型に整理する。 + +1. 名前と概念の責務混在 +2. 現在状態と履歴の責務混在 +3. 内部概念と外部ソース表現の混在 +4. 書込みモデルに対して保存構造が凝りすぎてゐる箇所 +5. 認証・セッション・BAN の責務分離不足 +6. 将来機能のための足場が現在のモデルに混ざってゐること + +--- + +## 3. v2 の基本方針 + +v2 は「機能を増やす」より先に、**境界を引き直す** ことを第一目的とする。 + +### 3.1 残すもの + +以下は v2 でも残す。 + +- Rails API + React フロントの基本構成 +- 投稿、タグ、Wiki を軸とした知識モデル +- 外部ソース同期 +- タグ階層 +- 監査ログ・履歴の重視 +- 類似度の事前計算 + +### 3.2 捨てるのではなく再分解するもの + +以下は廃止といふより、責務を再分解する。 + +- `tag_names` と `tags` の絡み方 +- `post_tags` に現在状態と履歴を同居させる構造 +- `nico:` プレフィクスを内部タグ空間に持ち込む方式 +- 行単位重複排除ベースの Wiki 保存構造 +- 引継ぎコードが認証・引継ぎ・セッションの責務を兼ねる方式 +- 汎用設定や将来機能の先置きテーブル + +### 3.3 v2 設計原則 + +#### 原則 1. 概念と名前を分ける + +内部的に一意な概念としてのタグと、人間が入力・検索・表示する名前は分ける。 + +#### 原則 2. 現在状態と履歴を分ける + +今ついてゐる関係と、かつて何が起きたかを同じテーブルに詰め込まない。 + +#### 原則 3. 内部知識と外部語彙を分ける + +ニコニコなどの外部タグは、内部タグの一種としてではなく、**外部ソースの語彙** として扱ふ。 + +#### 原則 4. 書込みモデルは素直にする + +編集や更新時に最も多い要求に対して、保存構造が複雑すぎる設計は避ける。 + +#### 原則 5. 監査は append-only を優先する + +監査のために現在値テーブルを複雑にするより、イベントログを素直に追加する。 + +#### 原則 6. 検索を第一級機能として扱ふ + +検索は controller の if 文の寄せ集めではなく、専用クエリレイヤとして設計する。 + +--- + +## 4. v1 の主要課題 + +### 4.1 タグ周りに責務が集まりすぎてゐる + +v1 では `tag_names` が単なる名称辞書ではなく、以下を同時に背負ってゐる。 + +- 実体名 / 別名 +- タグとの一対一対応 +- Wiki タイトルとの対応 +- canonical 化 +- 別名制約 +- プレフィクス制約 + +このため、タグ名に関する仕様変更がそのまま Wiki や別名ルールやカテゴリ整合性に波及する。 + +#### 問題点 + +- 名前レイヤと概念レイヤが癒着してゐる +- Wiki のページタイトル制約がタグ名制約に混ざる +- 外部タグや表記ゆれや redirect といった将来要件を吸ひ込みやすい +- 名前に関する例外処理が `tag_names` 周りに集中しやすい + +#### v2 方針 + +- 概念としてのタグは `tags` +- 検索・入力・旧名・同義語は `tag_aliases` +- Wiki はタグと結びつけられるが、タグ名そのものの制約とは分離 + +### 4.2 `post_tags` が現在値テーブルと履歴テーブルを兼ねてゐる + +v1 の `post_tags` は論理削除で履歴を保持し、作成者・削除者も持つ。 +これは初期設計としては合理的だが、長期では責務が重い。 + +#### 問題点 + +- 今ついてゐるタグ関係と、過去に何が起きたかが同じ器に入ってゐる +- 再付与、バッチ操作、手動操作、監査、集計で見たい粒度が異なる +- `post_count` 更新や一意制約の考へ方が複雑化する + +#### v2 方針 + +- 現在有効な関係は `post_taggings` +- 追加・削除イベントは `post_tag_events` +- `post_count` は `post_taggings` 基準で算出または同期する + +### 4.3 外部タグを内部タグ化してゐる + +v1 ではニコニコタグを `nico:` 付きの内部タグとして保持し、`nico_tag_relations` で内部タグと結びつけてゐる。 +これは ETL としては筋が良いが、表現としては内部タグ空間を汚しやすい。 + +#### 問題点 + +- 外部語彙と内部概念が同一モデルに見える +- 将来 YouTube, bilibili, Pixiv などを追加した際にモデルが拡張しづらい +- 外部タグ原文、取得元、取得時点、マッピング根拠の管理が曖昧になる + +#### v2 方針 + +- 外部語彙は `source_terms` +- 外部投稿と語彙の関係は `source_entry_terms` +- 外部語彙と内部タグの対応は `source_term_mappings` +- 内部タグ側には `nico:` のやうなプレフィクスを持ち込まない + +### 4.4 Wiki の保存構造が凝りすぎてゐる + +v1 Wiki は `wiki_pages`, `wiki_revisions`, `wiki_lines`, `wiki_revision_lines` に分かれ、行単位で重複排除を行ってゐる。 +思想としては美しいが、現在の開発体制と機能要求に対してはやや重い。 + +#### 問題点 + +- 保存構造の理解コストが高い +- バグ時の切り分けが難しい +- 「本文を保存し履歴を見る」といふ要求に対して内部構造が複雑すぎる +- 競合制御はまだ十分に API へ露出してをらず、複雑さに見合ふ価値が出切ってゐない + +#### v2 方針 + +- `wiki_pages` +- `wiki_revisions` に本文をそのまま持たせる +- 差分は表示時または保存時に計算 +- redirect は revision kind として維持可能 +- 競合制御は base revision 明示送信で実装する + +### 4.5 認証が軽量すぎて責務分離が弱い + +v1 は引継ぎコードを中心にユーザ継続利用を実現してゐる。軽量さ自体は良いが、引継ぎコードが次を兼ねてゐる。 + +- 認証要素 +- 継続ログインの鍵 +- セッション相当 +- ブラウザ間移行の鍵 + +#### 問題点 + +- セッション失効や監査や revoke が考へづらい +- BAN と組み合わせた制御の拡張余地が弱い +- 権限や認証強度の段階づけがしにくい + +#### v2 方針 + +- `auth_identities` に認証手段を保存 +- `sessions` で継続利用を管理 +- 引継ぎコードは認証方式の一種に降格 +- user / session / ban / ip linkage を分離 + +### 4.6 将来機能の足場が現在モデルに混ざってゐる + +`settings` や `parent_id` など、将来構想のための足場が現行スキーマに混ざってゐる。 + +#### 問題点 + +- 使ってゐる概念と使ってゐない概念の境目が曖昧になる +- API と UI と DB の整合性説明が難しくなる +- 「存在するが使ってゐない」が増えると仕様理解コストが上がる + +#### v2 方針 + +- 今使ふ概念だけを中核スキーマに残す +- 未来機能は issue / ADR / 設計メモに退避 +- 関係性は一般化できるものだけ一般化して先に作る + +--- + +## 5. v2 ドメインモデル + +### 5.1 中核モデル + +#### `posts` + +リンク対象の中核オブジェクト。 + +想定属性: + +- `id` +- `url` +- `title` +- `thumbnail_asset_id` または Active Storage 参照 +- `thumbnail_source_url` +- `original_created_from` +- `original_created_before` +- `created_by_user_id` +- `updated_by_user_id` +- `created_at` +- `updated_at` + +備考: + +- `parent_id` は廃止 +- 投稿同士の関係は `post_relations` へ移す + +#### `post_relations` + +投稿間関係を一般化する。 + +想定属性: + +- `source_post_id` +- `target_post_id` +- `relation_type` +- `created_by_user_id` +- `created_at` + +想定 `relation_type`: + +- `parent` +- `duplicate` +- `derived` +- `alternate` +- `source` + +### 5.2 タグモデル + +#### `tags` + +内部概念としてのタグ。 + +想定属性: + +- `id` +- `category` +- `canonical_label` +- `slug` +- `description` nullable +- `post_count` +- `created_at` +- `updated_at` + +補足: + +- `canonical_label` は表示上の正名 +- 一意性は `slug` または別管理キーで担保 +- 名前の表記ゆれは `tag_aliases` 側で受ける + +#### `tag_aliases` + +タグに紐づく入力名・別名・旧名・検索名。 + +想定属性: + +- `id` +- `tag_id` +- `name` +- `alias_type` +- `is_primary` +- `created_at` +- `updated_at` + +想定 `alias_type`: + +- `primary` +- `synonym` +- `old_name` +- `search_only` +- `redirect` + +#### `tag_implications` + +v1 をほぼ踏襲するが、タグ概念を基準に維持する。 + +### 5.3 投稿とタグの関係 + +#### `post_taggings` + +現在有効なタグ付与だけを持つ。 + +想定属性: + +- `id` +- `post_id` +- `tag_id` +- `created_by_user_id` nullable +- `created_source_type` +- `created_source_id` nullable +- `created_at` + +制約: + +- `(post_id, tag_id)` 一意 + +#### `post_tag_events` + +タグ付与イベントの監査ログ。 + +想定属性: + +- `id` +- `post_id` +- `tag_id` +- `event_type` +- `actor_user_id` nullable +- `actor_type` +- `source_type` +- `source_id` nullable +- `occurred_at` +- `metadata` json + +想定 `event_type`: + +- `add` +- `remove` +- `import_add` +- `import_remove` +- `merge_rewrite` + +### 5.4 Wiki + +#### `wiki_pages` + +想定属性: + +- `id` +- `slug` +- `title` +- `subject_type` nullable +- `subject_id` nullable +- `current_revision_id` +- `created_by_user_id` +- `updated_by_user_id` +- `created_at` +- `updated_at` + +補足: + +- タグ対応ページは `subject_type = 'Tag'` +- 独立ページも許容可能 + +#### `wiki_revisions` + +想定属性: + +- `id` +- `wiki_page_id` +- `kind` +- `body_markdown` nullable +- `redirect_target_page_id` nullable +- `base_revision_id` nullable +- `message` nullable +- `created_by_user_id` +- `created_at` + +想定 `kind`: + +- `content` +- `redirect` + +### 5.5 外部ソース同期 + +#### `sources` + +外部ソース定義。 + +想定属性: + +- `id` +- `code` (`niconico`, `youtube`, `pixiv` など) +- `name` +- `created_at` +- `updated_at` + +#### `source_entries` + +外部ソース上の個別エントリ。 + +想定属性: + +- `id` +- `source_id` +- `external_key` +- `canonical_url` +- `fetched_at` +- `raw_payload` json +- `created_at` +- `updated_at` + +#### `source_terms` + +外部ソース語彙。 + +想定属性: + +- `id` +- `source_id` +- `raw_name` +- `normalized_name` +- `created_at` +- `updated_at` + +#### `source_entry_terms` + +外部エントリと外部語彙の関係。 + +想定属性: + +- `source_entry_id` +- `source_term_id` +- `fetched_at` + +#### `source_term_mappings` + +外部語彙と内部タグの対応。 + +想定属性: + +- `id` +- `source_term_id` +- `tag_id` +- `mapping_type` +- `confidence` +- `created_by_user_id` nullable +- `created_at` + +想定 `mapping_type`: + +- `manual` +- `rule` +- `suggested` + +### 5.6 主体モデル + +#### `subjects` + +人物・団体・キャラクタ・投稿者等を統一的に扱ふ場合に導入する。 + +想定属性: + +- `id` +- `subject_type` +- `display_name` +- `created_at` +- `updated_at` + +#### `subject_identities` + +外部サービス上の主体識別子。 + +想定属性: + +- `id` +- `subject_id` +- `source_id` +- `external_code` +- `created_at` +- `updated_at` + +備考: + +- v1 `deerjikists` を一般化した形 +- v2 初期段階では必須ではないが、将来の拡張性は高い + +### 5.7 認証・権限 + +#### `users` + +想定属性: + +- `id` +- `display_name` +- `role` +- `status` +- `created_at` +- `updated_at` + +#### `auth_identities` + +想定属性: + +- `id` +- `user_id` +- `provider_type` +- `secret_hash` +- `last_used_at` +- `created_at` +- `updated_at` + +想定 `provider_type`: + +- `transfer_code` +- `passwordless_token` +- `oauth` など将来拡張 + +#### `sessions` + +想定属性: + +- `id` +- `user_id` +- `session_token_hash` +- `expires_at` +- `last_seen_at` +- `created_ip_id` nullable +- `user_agent` nullable +- `created_at` +- `updated_at` + +#### `ip_addresses` + +v1 を踏襲。ただし `user_ips` と `sessions` との役割分担を明確にする。 + +#### `user_bans` + +想定属性: + +- `id` +- `user_id` nullable +- `ip_address_id` nullable +- `reason` +- `starts_at` +- `ends_at` nullable +- `created_by_user_id` +- `created_at` + +--- + +## 6. v1 → v2 対応表 + +| v1 | v2 | 方針 | +| --- | --- | --- | +| `posts` | `posts` | 基本踏襲。ただし `parent_id` 廃止 | +| `post_tags` | `post_taggings` + `post_tag_events` | 現在状態と履歴を分離 | +| `tags` | `tags` | 概念として維持 | +| `tag_names` | `tag_aliases` へ再編 | 名前責務を分離 | +| `tag_implications` | `tag_implications` | 維持 | +| `nico_tag_relations` | `source_term_mappings` | 外部語彙対応へ再編 | +| `deerjikists` | `subject_identities` または専用互換層 | 一般化 | +| `wiki_pages` | `wiki_pages` | subject 参照型へ見直し | +| `wiki_revisions` + `wiki_lines` + `wiki_revision_lines` | `wiki_revisions` | 本文保存へ単純化 | +| `users` | `users` | role は維持 | +| `inheritance_code` | `auth_identities` | 認証手段へ分離 | +| `user_ips` | `user_ips` + `sessions` + `user_bans` | 責務整理 | +| `settings` | 一旦保留 or feature-specific table | 汎用設定を急がない | +| `user_post_views` | `user_post_views` | 維持可 | +| `post_similarities` | `post_similarities` | 維持 | +| `tag_similarities` | `tag_similarities` | 維持 | + +--- + +## 7. v1 データ移行方針 + +### 7.1 投稿 + +`posts` は基本的にそのまま移す。 + +#### 変換ルール + +- `url`, `title`, `thumbnail`, `thumbnail_base`, `original_created_from`, `original_created_before` は踏襲 +- `uploaded_user_id` は `created_by_user_id` へ写す +- `parent_id` は廃止し、存在するデータがあれば `post_relations` の `relation_type = 'parent'` に変換する + +### 7.2 タグ / タグ名 + +#### v1 構造 + +- `tags` が概念 +- `tag_names` が名称辞書 +- `tag_names.canonical_id` で別名 + +#### v2 変換ルール + +1. `tags` を v2 `tags` に移す +2. 各 tag の正名は v1 `tag_name.name` から `canonical_label` へ移す +3. 正名は同時に `tag_aliases` に `alias_type = 'primary'` として追加する +4. `canonical_id` を持つ `tag_names` は、対応する v2 `tag_aliases` に `alias_type = 'synonym'` として追加する +5. Wiki タイトルとしてのみ存在し、タグを持たない `tag_names` はタグへは変換せず、Wiki 側のページタイトルとして扱ふ + +#### 備考 + +この変換で、v1 の `tag_names` が担ってゐた「名前の辞書」「タグ実体」「Wiki タイトル」といふ三重責務を解体する。 + +### 7.3 投稿タグ付け + +#### v1 構造 + +- `post_tags.discarded_at IS NULL` が現在有効 +- 論理削除が履歴も兼ねる + +#### v2 変換ルール + +1. `discarded_at IS NULL` の行は `post_taggings` へ移す +2. 全 `post_tags` 行から `post_tag_events` を生成する + - 追加イベント: `created_at` + - 削除イベント: `discarded_at` がある場合のみ生成 +3. `created_user_id`, `deleted_user_id` は actor として転記する +4. `deleted_user_id IS NULL` かつ削除済みなら `actor_type = 'system'` とする + +#### 注意点 + +v1 の論理削除行は履歴として保存されてゐるので、監査情報は概ね保持可能である。 +ただし、v1 にはイベント ID が存在しないため、**完全な逐次イベント列としての厳密性は移行時に再構成する**。 + +### 7.4 ニコニコタグ連携 + +#### v1 構造 + +- `nico` カテゴリタグとして内部タグ化 +- `nico_tag_relations` で内部タグと結びつける + +#### v2 変換ルール + +1. `category = 'nico'` のタグは v2 内部タグとしては移さない +2. これらは `source_terms` へ変換する + - `source = 'niconico'` + - `raw_name` は `nico:` プレフィクスを外した形を基本とする +3. `nico_tag_relations` は `source_term_mappings` に変換する +4. 各投稿に付いてゐる `nico` タグは `source_entry_terms` に変換する +5. 同期バッチは v2 以降、`source_entry_terms` → `source_term_mappings` → `post_taggings` といふ流れで内部タグへ反映する + +#### 効果 + +- 内部タグ空間から `nico:` プレフィクスを除去できる +- 将来の他ソース追加時に同じ枠組みで扱へる + +### 7.5 ニジラー / deerjikists + +#### v1 構造 + +- `(platform, code)` を主キーとし `tag_id` に紐づけ + +#### v2 変換案 A: 当面互換維持 + +- v2 初期では `deerjikists` を暫定残置 +- 周辺だけ API 層でラップする + +#### v2 変換案 B: 一般化 + +1. `subjects` を作成 +2. `deerjikist` カテゴリのタグごとに subject を作る +3. `subject_identities` に `(platform, code)` を移す +4. タグとは `subject_tags` または `subject.primary_tag_id` で結ぶ + +#### 推奨 + +v2 初期の移行負荷を下げるなら **案 A**、中期で多用途化するなら **案 B**。 +本草案では、移行第一段階では案 A、v2.1 以降で B へ進めることを推奨する。 + +### 7.6 Wiki + +#### v1 構造 + +- `wiki_pages` +- `wiki_revisions` +- `wiki_lines` +- `wiki_revision_lines` + +#### v2 変換ルール + +1. `wiki_pages` を v2 `wiki_pages` に移す +2. 各 revision について本文を復元し、`wiki_revisions.body_markdown` に保存する +3. redirect revision は `kind = 'redirect'` のまま移す +4. `tag_name_id` 依存は `title` と `slug` および `subject_type`, `subject_id` へ再編する +5. タグと 1 対 1 に対応する Wiki は `subject_type = 'Tag'`, `subject_id = tag.id` に変換する + +#### 注意点 + +v2 では `wiki_lines` / `wiki_revision_lines` は廃止するため、行単位 dedupe は移行時点で終了する。 +これはデータ損失ではなく、保存形式の単純化である。 + +### 7.7 ユーザ認証 + +#### v1 構造 + +- `users.inheritance_code` +- `X-Transfer-Code` による継続認証 + +#### v2 変換ルール + +1. 全 `users` を v2 `users` に移す +2. `inheritance_code` は `auth_identities` へ移し、`provider_type = 'transfer_code'` とする +3. 現行クライアントの互換のため、一定期間 `X-Transfer-Code` を受け付ける互換 API を残す +4. サーバ側では受け付け時に session を発行し、徐々にセッション方式へ移行する + +#### 備考 + +この互換層を置くことで、フロントを一気に書き換へずに済む。 + +--- + +## 8. API 設計方針 + +### 8.1 互換方針 + +v2 は可能な限り**読み取り API を先に互換維持**し、書込み API から徐々に切り替へる。 + +#### 優先順位 + +1. 既存フロントが壊れないこと +2. 内部データの境界整理 +3. 最終的な API の純化 + +### 8.2 読み取り API + +以下は v1 互換をある程度維持可能である。 + +- `GET /posts` +- `GET /posts/:id` +- `GET /tags` +- `GET /tags/autocomplete` +- `GET /wiki` +- `GET /wiki/:id` +- `GET /wiki/title/:title` + +ただし内部では、v2 テーブルや query object を参照する実装へ切り替へる。 + +### 8.3 書込み API + +以下は v2 化の影響を受けやすい。 + +- `POST /posts` +- `PUT /posts/:id` +- `PUT /tags/nico/:id` 相当 +- `POST /wiki` +- `PUT /wiki/:id` +- `POST /users/verify` + +これらは互換 API を一旦残しつつ、内部実装を command / service object へ切り出してから v2 モデルへ接続する。 + +### 8.4 検索 API + +v2 では検索を query layer に集約する。 + +想定クラス: + +- `PostSearchQuery` +- `TagAutocompleteQuery` +- `WikiSearchQuery` +- `SourceTermMappingQuery` + +検索文法の目標例: + +```text +喜多郁代 合作 -R-18 source:niconico order:updated_at:desc +``` + +または将来的には次のやぅな DSL へ寄せる。 + +```text +tag:喜多郁代 tag:合作 -tag:R-18 source:niconico sort:updated +``` + +--- + +## 9. 実装移行ステップ + +### フェーズ 0. 準備 + +目的: + +- v2 テーブル群を追加するが、既存 API はまだ v1 を使ふ + +作業: + +- 新テーブル作成 +- 変換用 service / rake task 作成 +- 整合性確認用スクリプト作成 + +完了条件: + +- v1 本番データから v2 へバックフィルできる + +### フェーズ 1. 読み取り専用バックフィル + +目的: + +- v1 から v2 への初回コピーを行ふ + +作業: + +- `tags` / `tag_aliases` 生成 +- `post_taggings` / `post_tag_events` 生成 +- `wiki_revisions.body_markdown` 復元 +- `auth_identities` 生成 + +完了条件: + +- v1 と v2 で主要件数が整合する + +確認項目: + +- 投稿件数 +- タグ件数 +- 有効タグ付与件数 +- Wiki ページ数 +- revision 数 +- user 数 + +### フェーズ 2. 二重書込み + +目的: + +- v1 API を維持したまま v2 にも同時書込みする + +作業: + +- 投稿追加・更新時、v1 と v2 の両方へ反映 +- Wiki 更新時、v1 と v2 の両方へ反映 +- 認証時、互換層経由で session も発行 + +注意: + +- 二重書込みは最も事故りやすい層なので、対象を絞る +- 最初から全部ではなく、投稿 → Wiki → 認証の順で段階導入する + +### フェーズ 3. 読み取りの v2 切替 + +目的: + +- 一部 read API を v2 読みへ切り替へる + +推奨順: + +1. タグ補完 +2. Wiki 詳細 +3. 投稿履歴 +4. 投稿一覧 / 詳細 + +完了条件: + +- フロントの主要画面が v2 読みで正常動作する + +### フェーズ 4. 外部同期バッチ切替 + +目的: + +- `nico:sync` を v2 モデル前提で再実装する + +新フロー: + +1. 外部データ取得 +2. `source_entries` 更新 +3. `source_terms` / `source_entry_terms` 更新 +4. `source_term_mappings` に基づき内部タグ集合を導出 +5. `post_taggings` 同期 +6. `post_tag_events` 記録 + +### フェーズ 5. 旧モデル参照停止 + +目的: + +- API / batch / admin tool から v1 専用テーブルへの直接依存を止める + +対象: + +- `tag_names` +- `post_tags` +- `wiki_lines` +- `wiki_revision_lines` +- `users.inheritance_code` 直参照 +- `nico` カテゴリタグ依存ロジック + +### フェーズ 6. 旧モデル整理 + +目的: + +- v1 専用構造を正式に廃止する + +条件: + +- 監査・履歴・検索・同期の全主要系が v2 で動くこと +- 差分検証が一定期間安定してゐること + +--- + +## 10. フロントエンド影響 + +### 10.1 影響が小さい箇所 + +- 投稿一覧表示 +- 投稿詳細表示 +- Wiki 表示 +- タグ補完 UI + +これらは API 互換が維持できれば大きく変へずに済む。 + +### 10.2 影響が大きい箇所 + +- ニコニコ連携画面 +- タグ名 / 別名管理 UI +- ユーザ引継ぎ UI +- 投稿更新処理の排他制御 + +### 10.3 フロントで先に直すべきこと + +1. 書込み API を command 指向に合わせて整理する +2. `updated_at` 依存または revision id 依存の排他制御パラメータを明示送信できるやうにする +3. 引継ぎコード前提の利用状態をセッション前提へ徐々に移す + +--- + +## 11. 非互換変更 + +以下は v2 で非互換になる可能性が高い。 + +### 11.1 `nico:` タグの直接露出 + +内部タグとしての `nico:` は廃止する。 +よって、既存 API で `nico:` をタグとしてそのまま返してゐる箇所は、将来的に外部語彙情報として返す形へ変はる。 + +### 11.2 `tag_names` 直接依存 + +フロントや管理ツールが `tag_name_id` や `canonical_id` に依存してゐる場合、そのままでは使へなくなる。 + +### 11.3 `post_tags` の解釈 + +`discarded_at` ベースの関係履歴は `post_tag_events` へ移るため、旧仕様を前提にした直接 SQL は壊れる。 + +### 11.4 Wiki revision 構造 + +`wiki_lines` / `wiki_revision_lines` 前提の内部構造は消える。 + +--- + +## 12. リスク + +### 12.1 二重書込み不整合 + +最も危険。 +対策: + +- 対象操作を絞る +- 冪等設計を徹底する +- 差分検証バッチを持つ + +### 12.2 タグ移行時の alias 崩れ + +`tag_names.canonical_id` 由来の alias 変換で不整合が起きる可能性がある。 +対策: + +- 変換前後で検索結果差分を確認 +- 代表タグごとに snapshot 比較を行ふ + +### 12.3 ニコニコ同期の意味変化 + +v1 の `nico:` は内部タグでもあったため、完全分離すると挙動差が出る可能性がある。 +対策: + +- まずは read API 上で v1 互換表現を残す +- 内部的にのみ分離する + +### 12.4 Wiki 本文復元の取りこぼし + +行ベース保存から本文復元する処理は要注意。 +対策: + +- 全 revision の復元テスト +- tree hash と body hash の照合 + +### 12.5 認証移行時のログイン継続性 + +引継ぎコード方式から session 方式へ寄せる過程で、既存ユーザが弾かれると痛い。 +対策: + +- 当面は `X-Transfer-Code` 互換を残す +- セッション導入は透過的に始める + +--- + +## 13. 実装優先順位 + +v2 を全部一気にやる必要はない。 +優先順位は以下を推奨する。 + +### 優先度 A + +1. タグ体系の再分解 +2. `post_tags` の現在値 / 履歴分離 +3. 外部タグ表現の分離 + +### 優先度 B + +4. Wiki 保存構造の単純化 +5. 認証 / session / ban 整理 +6. 検索 query layer の独立 + +### 優先度 C + +7. deerjikists の一般化 +8. 設定や周辺機能の再整理 +9. 互換 API の整理・削除 + +--- + +## 14. この草案でまだ決め切ってゐないこと + +以下は本草案の時点では保留とする。 + +1. タグ slug を必須にするか +2. `subjects` を v2 初期で入れるか、v2.1 以降に送るか +3. `settings` を完全廃止するか、feature-specific table に分解するか +4. `post_count` を同期値として持つか、集計結果として算出するか +5. Wiki をタグ直結主体に寄せるか、独立ページを本格サポートするか + +--- + +## 15. 推奨する最初の一手 + +今すぐ着手するなら、最初の一手はこれがよい。 + +### 案 1. 最小リスク路線 + +- `post_taggings` +- `post_tag_events` +- `tag_aliases` + +だけ先に追加し、既存 API を壊さず移行を始める。 + +### 案 2. 将来最適路線 + +- 上記に加へて `source_terms` 系も一気に入れる +- `nico:` モデル依存を早めに絶つ + +#### 推奨 + +現実的には **案 1 → 案 2** の順がよい。 +理由は単純で、`post_tags` と `tag_names` が今の詰まりの中心だからである。 +ここを割るだけでも設計はかなり呼吸しやすくなる。 + +--- + +## 16. まとめ + +v1 の問題は、思想が弱いことではない。 +むしろ逆で、思想が強いがゆゑに責務が一箇所へ寄ってゐることが問題である。 + +v2 の目的は機能追加ではなく、以下の 3 つを回復することにある。 + +1. **概念と名前の分離** +2. **現在状態と履歴の分離** +3. **内部知識と外部ソースの分離** + +この 3 つを守れば、BTRC Hub は単なるリンク集の延長ではなく、長く育てられる知識基盤になる。 +逆にここを曖昧にしたまま機能を足し続けると、今後は毎回「どこに責務を置くのか」の議論からやり直すことになる。 + +本草案はまだ確定仕様ではない。 +ただし、**v1 を踏まへた v2 の叩き台としては十分に使へる水準** を狙ってゐる。 \ No newline at end of file