|
2 | 2 |
|
3 | 3 | ## 概要
|
4 | 4 |
|
5 |
| -このドキュメントは、Raspberry Pi Foundation が管理する国際的な Clubs API データベースと CoderDojo Japan のデータベースを連携させる機能の設計と実装について記載しています。 |
| 5 | +このドキュメントは、Raspberry Pi Foundation が管理する国際的な Clubs DB と CoderDojo Japan の Japan DB を連携させる機能の設計と実装について記載しています。 |
6 | 6 |
|
7 | 7 | ## 背景
|
8 | 8 |
|
9 |
| -### 現状の課題 |
10 |
| -- 新しい Dojo が追加されるたびに、DojoMap に表示させるために手動で CSV を更新する必要がある |
11 |
| -- この手動作業は時間がかかり、エラーが発生しやすい |
| 9 | +### 現状 |
| 10 | + |
| 11 | +- Clubs DB(Global Clubs)と Japan DB(CoderDojo Japan)が独立している |
| 12 | +- 両者を紐付ける ID がない |
12 | 13 |
|
13 | 14 | ### 解決策
|
14 |
| -Global Clubs データベースの ID(`global_club_id`)を CoderDojo Japan の Dojo と紐付けることで、データの同期を自動化する。 |
| 15 | +Clubs DB の ID(`global_club_id`)を Japan DB の Dojo と紐付ける。 |
15 | 16 |
|
16 | 17 | ## 設計決定
|
17 | 18 |
|
@@ -50,85 +51,130 @@ def fetch_global_clubs(organization_slug: 'coderdojo', after: nil)
|
50 | 51 |
|
51 | 52 | ### API エンドポイント
|
52 | 53 | - GraphQL: `https://clubs-api.raspberrypi.org/graphql`
|
53 |
| -- 認証: Bearer token (OAuth flow) |
| 54 | +- 認証: 不要(読み取り専用) |
54 | 55 | - レート制限: 60 req/min
|
55 | 56 |
|
56 | 57 | ### データモデル
|
57 | 58 |
|
58 | 59 | #### Dojo モデルの拡張
|
59 | 60 | ```ruby
|
60 |
| -# マイグレーション |
61 |
| -add_column :dojos, :global_club_id, :bigint |
62 |
| -add_index :dojos, :global_club_id, unique: true, where: 'global_club_id IS NOT NULL' |
| 61 | +# マイグレーション(必要なのはこれだけ!) |
| 62 | +# UUID 文字列(例: "18704b53-1042-4464-9d49-8820c6ff8c97") |
| 63 | +add_column :dojos, :global_club_id, :string, null: false |
| 64 | +add_index :dojos, :global_club_id, unique: true |
63 | 65 | ```
|
64 | 66 |
|
65 |
| -#### 同期管理テーブル |
66 |
| -```ruby |
67 |
| -create_table :global_club_syncs do |t| |
68 |
| - t.bigint :global_club_id, null: false |
69 |
| - t.datetime :last_seen_at |
70 |
| - t.string :sync_status |
71 |
| - t.timestamps |
72 |
| -end |
73 |
| -``` |
| 67 | +### マッピング戦略 |
| 68 | + |
| 69 | +#### 初期マッピング(一度だけ実行) |
| 70 | +1. Clubs DB から全 CoderDojo を取得 |
| 71 | +2. 既存の Dojo と手動で照合: |
| 72 | + - 名前は異なることが前提(Clubs DB は英語、Japan DB は日本語) |
| 73 | + - Japan DB には位置情報がないため、地理的照合は不可 |
| 74 | +3. 人間が判断して `global_club_id` を設定 |
74 | 75 |
|
75 |
| -### 同期戦略 |
| 76 | +**重要な仕様理解**: |
| 77 | +- **名前の違いは正常**: Clubs DB(英語話者向け)と Japan DB(日本語話者向け)で名前が異なるのは仕様 |
| 78 | +- **位置情報**: Japan DB にはなく、Clubs DB のみに存在 |
| 79 | +- **用途例**: DojoMap アプリで global_club_id を使って英語名→日本語名の変換を行う |
76 | 80 |
|
77 |
| -#### 増分同期 |
78 |
| -- `updatedAt` フィールドを使用して、前回の同期以降に更新されたクラブのみを取得 |
79 |
| -- 全データを毎回取得するのではなく、効率的な差分更新を実現 |
80 |
| -- 冪等性を保証(同じ操作を複数回実行しても結果が同じ) |
| 81 | +## 実装タスク(Issue #1616 より) |
81 | 82 |
|
82 |
| -#### マッチングアルゴリズム |
83 |
| -1. `global_club_id` で既存の Dojo を検索 |
84 |
| -2. 見つからない場合は、名前の類似度 + 地理的距離(1km 以内)で自動マッチング |
85 |
| -3. 曖昧な場合は手動レビューキューに追加 |
| 83 | +1. **Dojo モデルに `global_club_id` カラムを追加** |
| 84 | + ```ruby |
| 85 | + class AddGlobalClubIdToDojos < ActiveRecord::Migration[8.0] |
| 86 | + def change |
| 87 | + # UUID 文字列(例: "18704b53-1042-4464-9d49-8820c6ff8c97") |
| 88 | + add_column :dojos, :global_club_id, :string, null: false |
| 89 | + add_index :dojos, :global_club_id, unique: true |
| 90 | + end |
| 91 | + end |
| 92 | + ``` |
86 | 93 |
|
87 |
| -## 実装計画 |
| 94 | +2. **db/dojos.yaml の全 Dojo に `global_club_id` を追加** |
| 95 | + |
| 96 | +3. **Clubs DB と Dojo ID を紐付ける仕組みを作成** |
88 | 97 |
|
89 |
| -### フェーズ 1: 基盤整備 |
90 |
| -1. データベーススキーマの更新(`global_club_id` カラム追加) |
91 |
| -2. YAML ファイルの構造更新 |
92 |
| -3. モデルのバリデーション追加 |
93 | 98 |
|
94 |
| -### フェーズ 2: API 統合 |
95 |
| -1. `GlobalClubs::Client` クラスの実装 |
96 |
| -2. GraphQL クエリの実装 |
97 |
| -3. 認証設定(Rails credentials) |
98 | 99 |
|
99 |
| -### フェーズ 3: 同期機能 |
100 |
| -1. `GlobalClubs::SyncService` の実装 |
101 |
| -2. マッチングロジックの実装 |
102 |
| -3. エラーハンドリングとリトライ機構 |
103 | 100 |
|
104 |
| -### フェーズ 4: 運用 |
105 |
| -1. 定期実行ジョブの設定 |
106 |
| -2. 監視とアラートの実装 |
107 |
| -3. 手動レビュー機能(将来的に) |
108 | 101 |
|
109 | 102 | ## セキュリティ考慮事項
|
110 | 103 |
|
111 |
| -- Bearer token は Rails credentials に保存 |
112 |
| -- API 通信は HTTPS のみ |
113 |
| -- レート制限に対応(指数バックオフでリトライ) |
| 104 | +- HTTPS 通信(読み取り専用 API) |
114 | 105 |
|
115 | 106 | ## 運用上の注意点
|
116 | 107 |
|
117 | 108 | ### データの整合性
|
118 |
| -- ソフトデリート: Global Clubs API から削除されたクラブは `global_club_id = nil` に設定(Dojo レコードは削除しない) |
119 |
| -- 重複チェック: ユニーク制約により、同じ `global_club_id` を持つ複数の Dojo は作成できない |
| 109 | +- **ユニーク制約**: 同じ `global_club_id` を持つ複数の Dojo は作成できない |
| 110 | +- **NOT NULL 制約**: すべての Dojo に `global_club_id` が必須 |
| 111 | +- **手動修正可能**: 必要に応じて管理画面から修正 |
| 112 | + |
| 113 | + |
| 114 | +## 成功指標 |
| 115 | + |
| 116 | +1. **global_club_id カラムの追加完了** |
| 117 | +2. **既存 Dojo への ID 設定完了** |
| 118 | +3. **DojoMap への自動反映の実現**(Issue #1616 の目的) |
| 119 | + |
| 120 | +## シンプルなワークフロー |
120 | 121 |
|
121 |
| -### エラー処理 |
122 |
| -- ネットワークエラー: 自動リトライ(最大 3 回) |
123 |
| -- API エラー: エラーログに記録し、次回の同期で再試行 |
124 |
| -- マッチング失敗: 手動レビューキューに追加 |
| 122 | +### 新規 Dojo 追加時(申請フォームで対応) |
| 123 | + |
| 124 | +``` |
| 125 | +1. 申請時に Clubs DB での登録状況を確認してもらう |
| 126 | +2. global_club_id を申請フォームに記入(必須) |
| 127 | +3. db/dojos.yaml に global_club_id 付きで追加 |
| 128 | +``` |
| 129 | + |
| 130 | +### 初期マッピング(一度だけ実行するスクリプト) |
| 131 | + |
| 132 | +```ruby |
| 133 | +# scripts/map_global_clubs.rb |
| 134 | +# 既存の全 Dojo に global_club_id を設定 |
| 135 | + |
| 136 | +clubs = GlobalClubs.fetch_all_coderdojo_clubs # 読み取り専用 API |
| 137 | +dojos = Dojo.active |
| 138 | + |
| 139 | +puts "Clubs DB: #{clubs.count} clubs" |
| 140 | +puts "Japan DB: #{dojos.count} dojos" |
| 141 | +puts "" |
| 142 | +puts "Manual mapping needed:" |
| 143 | +puts "Clubs DB (English) | Japan DB (Japanese)" |
| 144 | +puts "---------------------------|--------------------" |
| 145 | + |
| 146 | +clubs.each do |club| |
| 147 | + # club.id は UUID 文字列(例: "18704b53-1042-4464-9d49-8820c6ff8c97") |
| 148 | + puts "#{club.name.ljust(26)} | ???" |
| 149 | +end |
| 150 | + |
| 151 | +# 手動で db/dojos.yaml に global_club_id を追加 |
| 152 | +# 例: global_club_id: "18704b53-1042-4464-9d49-8820c6ff8c97" |
| 153 | +``` |
125 | 154 |
|
126 |
| -## 今後の拡張可能性 |
127 | 155 |
|
128 |
| -- DojoMap との直接連携 |
129 |
| -- リアルタイム同期(Webhook 対応) |
130 |
| -- 他の組織のクラブとの連携 |
131 | 156 |
|
132 | 157 | ## 更新履歴
|
133 | 158 |
|
134 | 159 | - 2025-01-20: 初版作成(設計決定まで確定、技術仕様以降は暫定版)
|
| 160 | +- 2025-08-18: YAGNI原則に基づく大幅簡素化 |
| 161 | + - 不要なカラム削除(confidence, last_sync) |
| 162 | + - 継続的同期を削除(一度きりのマッピングに変更) |
| 163 | + - 複雑な監視・測定機能を削除 |
| 164 | + - シンプルなワークフローに変更 |
| 165 | + - CoderDojo運用の実態に合わせた現実的な設計に修正 |
| 166 | + |
| 167 | +## 実装チェックリスト |
| 168 | + |
| 169 | +### 必要最小限の実装 |
| 170 | +- [ ] `global_club_id` カラムの追加(string型、NOT NULL、UUID) |
| 171 | +- [ ] 読み取り専用の Clubs API クライアント実装 |
| 172 | +- [ ] 既存 Dojo への一括マッピングスクリプト |
| 173 | +- [ ] 申請フォームに global_club_id 入力欄追加(必須) |
| 174 | + |
| 175 | +### やらないこと(YAGNI) |
| 176 | +- ❌ APIトークン管理(読み取り専用なので不要) |
| 177 | +- ❌ 継続的な同期処理 |
| 178 | +- ❌ null を許可(全 Dojo に必須) |
| 179 | +- ❌ 複雑な監視ダッシュボード |
| 180 | +- ❌ 自動マッチング(手動で確実に設定) |
0 commit comments