active: true
derived: false
groups:
- CACHE
header: |
  命令型実行、スマートなデフォルト、およびワークスペース管理
level: 1.11
links:
- ARCH001: njFV9W_NXjx3dMCDMAVXf0g99DYN8tKwPHU5nP1tFhA=
- REQ001: 06OUEtR_P_Ao75pk04k0_gee2X4JLNEDpL76-c2iGgQ=
normative: true
ref: ''
reviewed: VNFCORxm5RACuIlxdAj2k7n1SbRWDtTFuqTc89n6s5k=
status: accepted
text: |
  # 命令型実行、スマートなデフォルト、およびワークスペース管理

  ## コンテキストと課題

  これまで `beautyspot` は、関数のキャッシュ化にデコレータ (`@project.task`) を使用する方法のみを提供していました。しかし、実際の利用シナリオにおいて以下の課題が浮き彫りになりました：

  1.  **サードパーティライブラリの利用**: ソースコードを変更できない関数（例: `pandas.read_csv`）や、外部APIクライアントのメソッドをキャッシュしたい場合、わざわざラッパー関数を定義する必要があり、記述が冗長になる。
  2.  **アドホックな解析**: ノートブック環境や一時的なスクリプトで試行錯誤する際、関数定義の手間がオーバーヘッドとなり、開発体験（DX）を損ねる。
  3.  **リソース管理の曖昧さ**: `Project` インスタンスが内部で保持する `ThreadPoolExecutor` が適切にシャットダウンされない場合、プロセスがハングするリスクがある。
  4.  **ディレクトリの汚染**: 生成されるデータベースファイル (`.db`) やBlobディレクトリ (`blobs/`) がプロジェクトルート直下に作成され、ディレクトリ構成が煩雑になる。
  5.  **設定の重複**: 複数のタスクで「常にBlob保存したい」「特定のバージョンを一律適用したい」といった場合、都度引数を指定するのは DRY (Don't Repeat Yourself) 原則に反する。
  6.  **`None` 結果のキャッシュ**: `time.sleep` のように戻り値が `None` である関数をキャッシュしようとすると、キャッシュミスと誤認され再実行されてしまうバグが存在した。

  ## 判断基準

  * **Flexibility**: ソースコードを変更できない外部関数やアドホックな処理に対しても、簡単にキャッシュを適用できること。
  * **Safety**: 非同期タスクや Executor のリソース管理を確実に行い、プロセスのクリーンな終了を保証すること。
  * **Clutter-free**: 生成されるキャッシュファイルを1箇所に集約し、プロジェクト構成をきれいに保つこと。
  * **Accuracy**: `None` を返す関数も含め、あらゆる戻り値を正しくキャッシュできること。

  ## 検討した選択肢

  * **Option 1**: 現状のデコレータ方式のみを維持し、ユーザーにラッパー関数の作成を求める。
  * **Option 2**: 命令的な `run` メソッドの導入、ワークスペースディレクトリ（`.beautyspot/`）による集約、コンテキストマネージャによるリソース管理を統合的に実装する。

  ## 決定事項

  採用した選択肢: **Option 2**.

  以下の包括的なアーキテクチャ変更を行います。

  ### 1. 命令的実行メソッド (`project.run`) の導入
  デコレータを使用せず、関数オブジェクトとその引数を渡すことで即座にキャッシュ付き実行を行う `run` メソッドを `Project` クラスに追加します。

  ```python
  result = project.run(func, arg1, arg2, _save_blob=True)
  ```

  * **引数設計**: キャッシュ制御用のオプション引数は、対象関数のキーワード引数 (`kwargs`) との衝突を避けるため、プレフィックス `_` を付与します（例: `_save_blob`, `_version`, `_content_type`）。

  ### 2. コンテキストマネージャとリソース管理

  `Project` クラスをコンテキストマネージャとして設計し、`with` ブロック内での利用を推奨パターンとします。これにより、ブロック終了時に確実に `shutdown()` が呼ばれ、スレッドプール等のリソースが解放されることを保証します。

  ### 3. 設定の階層化とスマートデフォルト

  `Project` 初期化時にデフォルト設定を受け入れ、個別の実行時にそれを継承・上書きできる仕組みを導入します。優先順位は「個別指定 > プロジェクトデフォルト > システムデフォルト」とします。

  ### 4. ワークスペースディレクトリによる集約

  すべての生成アーティファクト（DB、Blob）を、デフォルトで隠しディレクトリ `.beautyspot/` 配下に集約します。また、初期化時に `.gitignore` を自動生成し、キャッシュファイルがバージョン管理に含まれるのを防ぎます。

  ### 5. 番兵オブジェクトによるキャッシュ判定

  キャッシュヒットの判定において `None` を特別視せず、専用の番兵オブジェクト (`CACHE_MISS`) を導入することで、`None` を返す関数も正しくキャッシュ可能とします。

  ## 影響・結果

  * **ポジティブ**:
      * **高い柔軟性**: 既存のコードやライブラリ呼び出しに対して、ボイラープレートなしで即座にキャッシュを適用可能になった。
      * **安全なリソース管理**: `with` 文の使用により、プロセスハングのリスクが大幅に低減した。
      * **クリーンな環境**: プロジェクトルートが汚れず、Git管理からの除外も自動化されたため、ユーザーの負担が減った。
      * **正確なキャッシュ**: 副作用のみを目的とした関数（戻り値なし）も扱えるようになり、適用範囲が広がった。
  * **ネガティブ**:
      * **APIの複雑化**: `project.run` の引数に特殊なプレフィックスルール（`_`）が導入されたため、ドキュメントでの周知が必要。
      * **後方互換性の注意**: 引数処理の変更に伴い、内部実装での厳密なチェックが必要になった。
