# バッチ設計書 5-ReadingListsSyncOperation

## 概要

本ドキュメントは、Wikipedia iOSアプリにおけるリーディングリスト同期オペレーション（ReadingListsSyncOperation）のバッチ設計について記述します。

### 本バッチの処理概要

ReadingListsSyncOperationは、ユーザーのリーディングリストとエントリをローカルデバイスとWikimediaサーバー間で双方向同期するバッチ処理です。

**業務上の目的・背景**：ユーザーがログインしている場合、リーディングリスト（保存済み記事リスト）を複数デバイス間で同期する必要があります。これにより、ユーザーはどのデバイスからでも同じリーディングリストにアクセスでき、シームレスな読書体験を提供できます。オフライン時の変更もローカルに保存され、オンライン復帰時に同期されます。

**バッチの実行タイミング**：ログイン時、アプリ起動時、ユーザー操作時（リスト作成・編集・削除）、バックグラウンド同期時に実行されます。

**主要な処理内容**：
1. ローカルリスト・エントリのサーバーへのアップロード（作成/更新/削除）
2. サーバーからのリスト・エントリのダウンロード（作成/更新/削除）
3. バッチサイズ500件での分割処理
4. リスト分割機能（エントリ上限超過時）
5. 記事サマリーのフェッチと保存

**前後の処理との関連**：認証状態に依存し、SavedArticlesFetcherと連携して保存記事のオフラインダウンロードをトリガーします。

**影響範囲**：ReadingList、ReadingListEntry、WMFArticleテーブル、および認証状態に影響します。

## バッチ種別

データ同期 / 双方向レプリケーション

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | イベント駆動 |
| 実行時刻 | 随時 |
| 実行曜日 | 毎日 |
| 実行日 | 毎日 |
| トリガー | ユーザー操作 / ログイン / アプリ起動 / バックグラウンド同期 |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| 永続認証状態 | authStateIsPermanentがtrue |
| 同期エンドポイント利用可能 | wmf_isSyncRemotelyEnabledがtrue |
| syncStateフラグ | 同期が必要な状態フラグが設定されていること |

### 実行可否判定

syncStateに基づいて同期処理を決定します。needsSync、needsUpdate、needsRemoteEnable、needsRemoteDisable、needsLocalReset、needsLocalListClear、needsLocalArticleClearなどのフラグが処理を制御します。

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| dataStore | MWKDataStore | Yes | - | データストア参照 |
| apiController | ReadingListsAPIController | Yes | - | APIコントローラー |
| readingListsController | ReadingListsController | Yes | - | リーディングリストコントローラー |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| CoreData (ReadingList) | DB | ローカルリーディングリスト |
| CoreData (ReadingListEntry) | DB | ローカルリストエントリ |
| Wikipedia API | REST API | リモートリーディングリスト・エントリ |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| CoreData (ReadingList) | DB | 同期されたリーディングリスト |
| CoreData (ReadingListEntry) | DB | 同期されたリストエントリ |
| CoreData (WMFArticle) | DB | 記事プレビュー情報 |
| Wikipedia API | REST API | アップロードされたリスト・エントリ |

### 出力ファイル仕様

ファイル出力はありません。

## 処理フロー

### 処理シーケンス

```
1. execute()呼び出し
   └─ syncedReadingListsCountとsyncedReadingListEntriesCountを初期化
2. バックグラウンドCoreDataコンテキストで処理
   └─ dataStore.performBackgroundCoreDataOperation
3. syncStateチェック
   └─ 各種フラグに応じた処理分岐
4. リモート無効化処理（needsRemoteDisable）
   └─ apiController.teardownReadingLists
5. ランダムリスト作成（needsRandomLists）
   └─ テスト用ランダムリスト生成
6. ローカルリセット（needsLocalReset）
   └─ readingListID/readingListEntryIDをnullに
7. ローカルリストクリア（needsLocalListClear）
   └─ デフォルト以外のリストを削除マーク
8. ローカル記事クリア（needsLocalArticleClear）
   └─ 保存記事を解除
9. ローカルのみ同期（認証なし時）
   └─ executeLocalOnlySync
10. リモート有効化（needsRemoteEnable）
    └─ apiController.setupReadingLists
11. フル同期（needsSync）
    └─ executeFullSync
12. 差分更新（needsUpdate）
    └─ executeUpdate
13. ローカル変更のアップロード
    └─ processLocalUpdates
```

### フローチャート

```mermaid
flowchart TD
    A[execute] --> B[バックグラウンドコンテキスト取得]
    B --> C{authStateIsPermanent?}
    C -->|No| D[executeLocalOnlySync]
    C -->|Yes| E{syncState確認}
    E --> F{needsRemoteDisable?}
    F -->|Yes| G[teardownReadingLists]
    F -->|No| H{needsRemoteEnable?}
    G --> H
    H -->|Yes| I[setupReadingLists]
    H -->|No| J{needsSync?}
    I --> J
    J -->|Yes| K[executeFullSync]
    J -->|No| L{needsUpdate?}
    K --> M[processLocalUpdates]
    L -->|Yes| N[executeUpdate]
    L -->|No| O[終了]
    N --> M
    M --> P[CoreData保存]
    P --> O
    D --> O
```

## データベース操作仕様

### 操作別データベース影響一覧

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| フル同期 | ReadingList | INSERT/UPDATE/DELETE | リモートリストとの同期 |
| フル同期 | ReadingListEntry | INSERT/UPDATE/DELETE | リモートエントリとの同期 |
| ローカル更新アップロード | ReadingList | UPDATE | readingListIDの設定 |
| ローカル更新アップロード | ReadingListEntry | UPDATE | readingListEntryIDの設定 |
| 記事作成 | WMFArticle | INSERT/UPDATE | 記事サマリーの保存 |

### テーブル別操作詳細

#### ReadingList

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT/UPDATE | readingListID | サーバーから取得したID | リモートID |
| INSERT/UPDATE | name | リスト名 | 正規化済み |
| INSERT/UPDATE | readingListDescription | リスト説明 | オプション |
| UPDATE | isUpdatedLocally | false | 同期完了後 |
| UPDATE | errorCode | APIエラーコード | エラー時 |

#### ReadingListEntry

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT/UPDATE | readingListEntryID | サーバーから取得したID | リモートID |
| INSERT/UPDATE | articleKey | 記事キー | URL派生 |
| INSERT/UPDATE | displayTitle | 表示タイトル | 記事から取得 |
| UPDATE | isUpdatedLocally | false | 同期完了後 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| .notSetup | APIReadingListError | 同期が未設定 | 同期を無効化 |
| .needsFullSync | APIReadingListError | フル同期が必要 | needsSyncフラグを設定して再同期 |
| .cancelled | ReadingListsOperationError | 処理キャンセル | タスクキャンセルして終了 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | なし（次回同期で再試行） |
| リトライ間隔 | ユーザー操作または定期同期時 |
| リトライ対象エラー | ネットワークエラー |

### 障害時対応

エラー発生時はCoreDataの変更を保存してから終了します。needsFullSyncエラーの場合は自動的に再同期をスケジュールします。

## トランザクション仕様

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | バッチ処理単位 |
| コミットタイミング | バッチ完了時、500件ごと |
| ロールバック条件 | CoreData保存エラー時 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | リスト数百件、エントリ数千件 |
| 目標処理時間 | 数秒〜数十秒 |
| メモリ使用量上限 | バッチサイズ500件で分割処理 |

## 排他制御

- ReadingListsOperationとして実行され、OperationQueueで排他制御
- isCancelledフラグでキャンセル可能

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| エラーログ | 同期エラー時 | "error during sync operation: \(error)" |
| エラーログ | 保存エラー時 | "error during sync save: \(error)" |
| エラーログ | エントリ取得エラー時 | "Error fetching entries for reading list with ID" |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| 同期エラー | - | DDLogError経由 |

## 備考

- WMFReadingListBatchSizePerRequestLimit = 500件
- WMFReadingListCoreDataBatchSizeはCoreData操作のバッチサイズ
- リストエントリ数が上限を超えた場合、自動的にリストを分割
- 言語バリアントを考慮した記事URL処理
- WMFArticleSavedStateMigrationManagerとの連携で新規同期記事をWMFDataに登録
