全体処理フロー(ユースケース手順)
0) 起動〜前処理
- CLI 引数を解釈(
-i <yaml>必須) - ロギング初期化(ログは stderr / stdout とは分離)
- 入力ファイル存在チェック(無ければ入力エラーで即時終了)
1) YAML 読み込み(入力→ドメイン)
- YAML を読み込み、ドメインモデル(Book/Chapter/Page)へ変換
- ここでトリム等の正規化(前後 trim)は 変換時に実施(以後は正規化済みを前提にする)
2) ドメイン検証(API を叩く前に止める)
- 必須チェック・型チェック・重複チェックなど(要件にある “順序が意味を持つ” を前提に、順序自体は保持)
- 検証 NG は 入力エラー として即時終了(API は一切呼ばない)
3) 事前取得(同一性判定に必要な最小情報だけ)
- 対象 Book の既存構造(Book直下 pages / chapters / chapter配下 pages)を API で取得
- 取得するのは同一性判定に必要な最小フィールド(例: id, name, parent 等)に限定
4) Book 直下ページ処理(YAMLの順序通り)
-
YAML の
pagesを上から順に処理 -
各ページについて:
- 同一性判定(既存があるか)
- あれば SKIP
- 無ければ CREATE → OK
5) Chapter 処理(YAMLの順序通り)
-
YAML の
chaptersを上から順に処理 -
各 Chapter について:
- 同一性判定(既存があるか)
- 無ければ CREATE → OK
- あれば SKIP
- 以後、その Chapter コンテナ(id)を確定させる
6) Chapter 配下ページ処理(章ごと、YAMLの順序通り)
-
各 Chapter の
pagesを上から順に処理 -
各ページについて:
- 同一性判定
- あれば SKIP
- 無ければ CREATE → OK
7) 終了
- 全処理が成功/スキップで完了したら終了コード 0
3.2 エラー発生時の即時終了ルール
即時終了の原則を先に固定しておくと、実装がブレない。
A. 入力エラー(YAML/検証)は「即時終了・部分実行なし」
- YAML パース不能
- 必須項目不足
- 型不正
- 重複や同一性ルール上の矛盾(設計で決めた NG 条件)
- → 終了コード:入力エラー(例: 2)、stdout には最後に ERROR を出す(下記参照)
B. API 実行エラーは「即時終了」
- 途中まで作ってしまっても “破壊操作なし・再実行で復旧” が前提なので、基本は即時終了でよい
- → 終了コード:実行エラー(例: 1)
C. 例外(想定外)は「即時終了」
- → 実行エラー(1)扱いでよい(内部ログにスタックトレース)
3.3 API リトライ適用範囲(どこでやるかも含めて固定)
リトライの配置
- BookStackClient(インフラ層)に集約 ユースケース層は「API を呼ぶ/呼ばない」の判断だけを持つ。
リトライ対象(推奨)
-
ネットワーク系(タイムアウト、接続失敗、DNS 等)
-
5xx(サーバエラー)
-
429(レート制限)※
Retry-Afterがあれば尊重 -
GET/POST とも対象にできるが、POST は冪等性が崩れやすいので 注意事項を仕様に書く:
- “作成 API が二重作成を起こし得る” 場合:POST は原則リトライしない か、あるいは「作成直後に同一性再チェックして二重作成を抑止」などの方針を明文化(どちらかに寄せて決め打つ)
リトライ非対象(推奨)
- 4xx(400/401/403/404/422 など)※入力や権限や存在の問題なので再試行しても改善しない
- JSON decode 失敗などのアプリ側バグ疑い
3.4 標準出力イベント(OK / SKIP / ERROR)の定義
stdout は「機械/人が追える最小ログ」に限定して、ログ本体は logger 側へ。
1行1イベント(推奨フォーマット例)
OK <kind> <path> <id?> <name>SKIP <kind> <path> <id?> <name> (reason=<...>)ERROR <phase> <message>
ここで:
<kind>:BOOK_PAGE | CHAPTER | CHAPTER_PAGE<path>: 例book/pages[03]chapters[01]chapters[01]/pages[02]<id?>: 作成した/既存の id を出せるなら出す(後追いデバッグが楽)
ERROR 出力のタイミング
- 入力エラー:検証で落ちた時点で ERROR 1行出して終了
- API エラー:リトライ尽きた時点で ERROR 1行出して終了
3.5 「どのモジュールにどの機能を持たせるか」案(責務分割)
実装前提の分割案。名前は例で、責務境界が重要。
bookstack_skeleton.main(エントリポイント)
- CLI 引数の解釈
- ログ初期化
- ユースケース呼び出し
- 例外整形・終了コード返却
bookstack_skeleton.loader
- YAML の読み込み
- ドメインへ変換(正規化:trim 等)
- 「YAML構造→ドメイン」の責務のみ(検証は別)
bookstack_skeleton.domain(dataclass 群)
BookSpec / ChapterSpec / PageSpec等(命名は任意)- ドメイン不変条件(軽いバリデーションはここでも可。ただし“入力エラー一覧”を出したいなら validator に寄せる方が実務的)
bookstack_skeleton.validator
- ドメイン検証(必須・型・重複・規約)
- エラーは「複数件まとめて」返せる形が望ましい(ユーザーの修正コストが下がる)
bookstack_skeleton.usecase.apply(ユースケース本体)
- 処理順の固定(Book直下→Chapter→Chapter配下)
- 同一性判定の呼び出し
- “作る/作らない” の意思決定
- stdout イベントの発行(Reporter 経由でも良い)
bookstack_skeleton.identity
- 同一性判定ルール(Book直下ページ / Chapter / Chapter配下ページ)
- “SKIP 条件” をここで一元化(= 判定結果に reason を持たせる)
bookstack_skeleton.client(BookStackClient)
- API 呼び出しの薄いラッパ
- リトライ(範囲・回数・待機)をここに集約
- 「最小フィールドだけ取る」など取得最適化もここ
bookstack_skeleton.reporter(任意)
- stdout のイベント整形(OK/SKIP/ERROR の1行化)
- main/usecase から分離したい場合のみ