局所トレーサビリティ — グループ: DI

# 局所トレーサビリティビュー グループ: DI

生成日時: 2026-03-13 23:59:51

### 対象アイテム

7

### REQ

1

### ARCH

1

### SPEC

1

### TST

1

### IMPL

1

### ADR

2

### レビュー済

7/7

### Suspect

0

### グループ

DI

## トレーサビリティマトリクス

✓=レビュー済 ○=未レビュー ⚠=Suspect（複数同時表示あり。IDクリックで詳細へ）

グループ | REQ | ARCH | SPEC | TST | IMPL | ADR
--- | --- | --- | --- | --- | --- | ---
DI | REQ012 ✓ 全コンポーネント（DB、ストレージ、シリアライザ、リミッター等）が `Protocol/ABC` に基づく依存注入により差し替え可能であること。ファクトリ関数が... | ARCH012 ✓ ## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|構築| B[core.Spot] A ... | SPEC022 ✓ ## インターフェース ```python def Spot( name: str = "default", storage_path: st... | TST022 ✓ ## 目的 `bs.Spot()` ファクトリ関数は全コンポーネントの DI 配線を担う唯一のパブリックエントリポイントであり、デフォルト構成の正確性とカスタ... | IMPL022 ✓ ## 実装概要 `beautyspot` パッケージのメインエントリポイントとなる `Spot` ファクトリ関数の実装。 引数として渡された各コンポーネント（... | ADR009 ✓ # Database Dependency Injection and Abstraction ## Context and Problem Statemen...
DI | REQ012 ✓ 全コンポーネント（DB、ストレージ、シリアライザ、リミッター等）が `Protocol/ABC` に基づく依存注入により差し替え可能であること。ファクトリ関数が... | ARCH012 ✓ ## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|構築| B[core.Spot] A ... | SPEC022 ✓ ## インターフェース ```python def Spot( name: str = "default", storage_path: st... | TST022 ✓ ## 目的 `bs.Spot()` ファクトリ関数は全コンポーネントの DI 配線を担う唯一のパブリックエントリポイントであり、デフォルト構成の正確性とカスタ... | IMPL022 ✓ ## 実装概要 `beautyspot` パッケージのメインエントリポイントとなる `Spot` ファクトリ関数の実装。 引数として渡された各コンポーネント（... | ADR026 ✓ # Factory Function for Default Dependency Injection ## Context and Problem Stat...

## カバレッジ（局所）

リンク方向 | カバー数 | カバー率 | 未カバー
--- | --- | --- | ---
ARCH → REQ | 1 / 1 | 100.0% | —
SPEC → ARCH | 1 / 1 | 100.0% | —
TST → SPEC | 1 / 1 | 100.0% | —
IMPL → SPEC | 1 / 1 | 100.0% | —
ADR → REQ | 1 / 1 | 100.0% | —

## アイテム詳細

### REQ012 REQ {h(g)} ✓ レビュー済

全コンポーネント（DB、ストレージ、シリアライザ、リミッター等）が `Protocol/ABC` に基づく依存注入により差し替え可能であること。ファクトリ関数がデフォルトの組み立てを提供すること。

全コンポーネント（DB、ストレージ、シリアライザ、リミッター等）が `Protocol/ABC` に基づく依存注入により差し替え可能であること。ファクトリ関数がデフォルトの組み立てを提供すること。

親: —

子: ADR009, ADR026, ARCH012

### ARCH012 ARCH {h(g)} ✓ レビュー済

## コンポーネント構成

    graph TD
      A[bs.Spot Factory] -->|構築| B[core.Spot]
      A -->|DI| C[TaskDBBase]
      A -->|DI| D[BlobStorageBase]
      A -->|DI| E[SerializerProtocol]
      A -->|DI| F[StoragePolicyProtocol]
      A -->|DI| G[LimiterProtocol]

## コンポーネント責務

コンポーネント | 責務 | インターフェース
--- | --- | ---
bs.Spot (Factory) | 環境（名前、パス等）に応じたデフォルトコンポーネントの選定と組み立て | `bs.Spot()`
core.Spot | 注入された依存関係を使用して、キャッシュロジックをオーケストレーションする | `mark()`, `cached_run()`
Protocols / ABCs | 各コンポーネントが満たすべき契約（インターフェース）の定義 | `SerializerProtocol` 等

## 技術選定

技術領域 | 選定 | 理由
--- | --- | ---
パターン | Constructor Injection | 依存関係を明示的に渡し、テスト時のMock差し替えを容易にする
インターフェース | Protocol (Duck Typing) | 厳密な継承を強制せず、構造的部分型によりサードパーティ実装を受け入れ
デフォルト設定 | 規約より構成 (CoC) | `.beautyspot/` ディレクトリを基点とした標準パスを自動生成

## 非機能要件方針

- 結合度: `core.Spot` は具体的なクラス名（SQLite等）を知らず、Protocolのみに依存する
- テスト容易性: メモリ上のDBや仮装ストレージを注入することで、高速な単体テストが可能
- 拡張性: ユーザーが独自のシリアライザやストレージを自作して注入可能

## コンポーネント構成 ```mermaid graph TD A[bs.Spot Factory] -->|構築| B[core.Spot] A -->|DI| C[TaskDBBase] A -->|DI| D[BlobStorageBase] A -->|DI| E[SerializerProtocol] A -->|DI| F[StoragePolicyProtocol] A -->|DI| G[LimiterProtocol] ``` ## コンポーネント責務 | コンポーネント | 責務 | インターフェース | |---|---|---| | bs.Spot (Factory) | 環境（名前、パス等）に応じたデフォルトコンポーネントの選定と組み立て | `bs.Spot()` | | core.Spot | 注入された依存関係を使用して、キャッシュロジックをオーケストレーションする | `mark()`, `cached_run()` | | Protocols / ABCs | 各コンポーネントが満たすべき契約（インターフェース）の定義 | `SerializerProtocol` 等 | ## 技術選定 | 技術領域 | 選定 | 理由 | |---|---|---| | パターン | Constructor Injection | 依存関係を明示的に渡し、テスト時のMock差し替えを容易にする | | インターフェース | Protocol (Duck Typing) | 厳密な継承を強制せず、構造的部分型によりサードパーティ実装を受け入れ | | デフォルト設定 | 規約より構成 (CoC) | `.beautyspot/` ディレクトリを基点とした標準パスを自動生成 | ## 非機能要件方針 - **結合度**: `core.Spot` は具体的なクラス名（SQLite等）を知らず、Protocolのみに依存する - **テスト容易性**: メモリ上のDBや仮装ストレージを注入することで、高速な単体テストが可能 - **拡張性**: ユーザーが独自のシリアライザやストレージを自作して注入可能

親: REQ012

子: SPEC022

### SPEC022 SPEC {h(g)} ✓ レビュー済

## インターフェース

    def Spot(
        name: str = "default",
        storage_path: str | Path | None = None,
        serializer: SerializerProtocol | None = None,
        limiter: LimiterProtocol | None = None,
        # ...その他の依存
    ) -> core.Spot: ...

## 振る舞い

1. パス解決: `storage_path` が未指定の場合、`.beautyspot/` 以下のプロジェクト名ディレクトリを使用する
2. DB初期化: `SQLiteTaskDB` を生成し、マイグレーションを実行
3. ストレージ構成: URI（`s3://`等）を判定し、適切な `BlobStorage` 実装を生成
4. インスタンス化: 全てのコンポーネントを `core.Spot` のコンストラクタに注入して返す

## パラメータ詳細

パラメータ | デフォルト値の導出
--- | ---
`db` | `.beautyspot/{name}.db`
`blobs` | `.beautyspot/blobs/{name}/`
`serializer` | `MsgpackSerializer()`
`policy` | `WarningOnlyPolicy` (閾値10MB)

## エラーハンドリング

- ディレクトリ作成失敗: 権限等の理由でストレージディレクトリが作成できない場合は、早期に `OSError` を送出する。

## エッジケース

- グローバルシングルトン: 同じ `name` で複数回呼び出した場合も、原則として新しいインスタンスを返すが、DBファイルへの接続は共有される（SQLiteのWALモードを活用）。

## インターフェース ```python def Spot( name: str = "default", storage_path: str | Path | None = None, serializer: SerializerProtocol | None = None, limiter: LimiterProtocol | None = None, # ...その他の依存 ) -> core.Spot: ... ``` ## 振る舞い 1. **パス解決**: `storage_path` が未指定の場合、`.beautyspot/` 以下のプロジェクト名ディレクトリを使用する 2. **DB初期化**: `SQLiteTaskDB` を生成し、マイグレーションを実行 3. **ストレージ構成**: URI（`s3://`等）を判定し、適切な `BlobStorage` 実装を生成 4. **インスタンス化**: 全てのコンポーネントを `core.Spot` のコンストラクタに注入して返す ## パラメータ詳細 | パラメータ | デフォルト値の導出 | |---|---| | `db` | `.beautyspot/{name}.db` | | `blobs` | `.beautyspot/blobs/{name}/` | | `serializer` | `MsgpackSerializer()` | | `policy` | `WarningOnlyPolicy` (閾値10MB) | ## エラーハンドリング - **ディレクトリ作成失敗**: 権限等の理由でストレージディレクトリが作成できない場合は、早期に `OSError` を送出する。 ## エッジケース - **グローバルシングルトン**: 同じ `name` で複数回呼び出した場合も、原則として新しいインスタンスを返すが、DBファイルへの接続は共有される（SQLiteのWALモードを活用）。

親: ARCH012

子: IMPL022, TST022

### TST022 TST {h(g)} ✓ レビュー済

## 目的

`bs.Spot()` ファクトリ関数は全コンポーネントの DI 配線を担う唯一のパブリックエントリポイントであり、デフォルト構成の正確性とカスタム実装の差し替え可能性がシステム全体の柔軟性と正確性を決定する。

## 検証観点

- デフォルト構成: 引数未指定時に `SQLiteTaskDB`, `LocalStorage`, `MsgpackSerializer`, `TokenBucket`, `WarningOnlyPolicy` が自動注入されること。ワークスペースディレクトリ（`.beautyspot/`）が自動作成されること
- カスタム DB 注入: `TaskDBBase` / `TaskDBCore` プロトコル準拠のカスタムDBを `db=` で注入し、正常にキャッシュ操作が行えること
- カスタム Storage 注入: `BlobStorageBase` 準拠のモック Storage を `storage_backend=` で注入し、Blob保存が委譲されること
- カスタム Serializer 注入: `SerializerProtocol` 準拠のカスタム Serializer を注入し、シリアライズ処理が差し替わること
- 型推論: `bs.Spot()` の戻り値型が pyright / mypy で正しく推論されること
- Protocol 不準拠の拒否: 必須メソッドを欠くオブジェクトを注入した場合に型チェックエラーまたは実行時エラーが発生すること

## 目的 `bs.Spot()` ファクトリ関数は全コンポーネントの DI 配線を担う唯一のパブリックエントリポイントであり、デフォルト構成の正確性とカスタム実装の差し替え可能性がシステム全体の柔軟性と正確性を決定する。 ## 検証観点 - **デフォルト構成**: 引数未指定時に `SQLiteTaskDB`, `LocalStorage`, `MsgpackSerializer`, `TokenBucket`, `WarningOnlyPolicy` が自動注入されること。ワークスペースディレクトリ（`.beautyspot/`）が自動作成されること - **カスタム DB 注入**: `TaskDBBase` / `TaskDBCore` プロトコル準拠のカスタムDBを `db=` で注入し、正常にキャッシュ操作が行えること - **カスタム Storage 注入**: `BlobStorageBase` 準拠のモック Storage を `storage_backend=` で注入し、Blob保存が委譲されること - **カスタム Serializer 注入**: `SerializerProtocol` 準拠のカスタム Serializer を注入し、シリアライズ処理が差し替わること - **型推論**: `bs.Spot()` の戻り値型が pyright / mypy で正しく推論されること - **Protocol 不準拠の拒否**: 必須メソッドを欠くオブジェクトを注入した場合に型チェックエラーまたは実行時エラーが発生すること

references: tests/integration/core/test_dependency_injection.py

親: SPEC022

子: —

### IMPL022 IMPL {h(g)} ✓ レビュー済

## 実装概要

`beautyspot` パッケージのメインエントリポイントとなる `Spot` ファクトリ関数の実装。 引数として渡された各コンポーネント（DB、Serializer、Storage等）を依存性注入（DI）で 解決し、デフォルトのコンポーネント（`SQLiteTaskDB`, `MsgpackSerializer`, `LocalStorage`等）を インスタンス化して `CacheManager` と `_Spot` コアエンジンを組み立てる。

## 設計判断

### Factory 関数による Composition

`_Spot` クラス自体のコンストラクタは複雑な依存関係を要求するが、 ユーザー向けに `Spot()` 関数を提供することで、通常は `name` を渡すだけで 「ゼロ設定」で動作するようにカプセル化している。 同時に、高度なユーザーは各コンポーネントを自由に差し替え可能な DI アーキテクチャを維持している。

### DB ライフサイクルの委譲

`Spot()` 関数内でデフォルトのDB（`SQLiteTaskDB`）を自動生成した場合、 `spot._owns_db = True` フラグを立て、Spotエンジンのシャットダウン時に DBも自動でクローズされるようにする。一方、ユーザーが明示的に `db=` を 渡した場合は、DBのライフサイクル管理は呼び出し元に委ねる（勝手に閉じない）。

## 実装メモ

- Storage Policy は `save_blob` フラグや引数によって `WarningOnlyPolicy`, `AlwaysBlobPolicy` 等に解決される
- `__all__` に各種プロトコルやデフォルト実装、例外クラスをエクスポートし、 ライブラリとしての公開API境界を明確に定義している

## 実装概要 `beautyspot` パッケージのメインエントリポイントとなる `Spot` ファクトリ関数の実装。 引数として渡された各コンポーネント（DB、Serializer、Storage等）を依存性注入（DI）で 解決し、デフォルトのコンポーネント（`SQLiteTaskDB`, `MsgpackSerializer`, `LocalStorage`等）を インスタンス化して `CacheManager` と `_Spot` コアエンジンを組み立てる。 ## 設計判断 ### Factory 関数による Composition `_Spot` クラス自体のコンストラクタは複雑な依存関係を要求するが、 ユーザー向けに `Spot()` 関数を提供することで、通常は `name` を渡すだけで 「ゼロ設定」で動作するようにカプセル化している。 同時に、高度なユーザーは各コンポーネントを自由に差し替え可能な DI アーキテクチャを維持している。 ### DB ライフサイクルの委譲 `Spot()` 関数内でデフォルトのDB（`SQLiteTaskDB`）を自動生成した場合、 `spot._owns_db = True` フラグを立て、Spotエンジンのシャットダウン時に DBも自動でクローズされるようにする。一方、ユーザーが明示的に `db=` を 渡した場合は、DBのライフサイクル管理は呼び出し元に委ねる（勝手に閉じない）。 ## 実装メモ - Storage Policy は `save_blob` フラグや引数によって `WarningOnlyPolicy`, `AlwaysBlobPolicy` 等に解決される - `__all__` に各種プロトコルやデフォルト実装、例外クラスをエクスポートし、 ライブラリとしての公開API境界を明確に定義している

references: src/beautyspot/__init__.py

親: SPEC022

子: —

### ADR009 ADR {h(g)} ✓ レビュー済

# Database Dependency Injection and Abstraction

## Context and Problem Statement / コンテキスト

これまでの `beautyspot` (`v0.x`) は、`Project` クラス内部で `TaskDB` (SQLite実装) をハードコードしてインスタンス化していた。

    # v0.x implementation
    self.db = TaskDB(self.db_path)

この設計には以下の課題がある：

1. 拡張性の欠如: SQLite 以外のデータベース（PostgreSQL, DuckDB, In-Memory DB等）を使いたくても、ライブラリのコードを書き換えない限り不可能である。
2. テストの制約: ユニットテスト時に、ファイルシステムに依存しないモックDBやオンメモリDBへの差し替えが困難である。

v1.0.0 では、ユーザー体験（DX）としての「手軽さ（パスを指定するだけ）」を維持しつつ、アーキテクチャレベルでの柔軟性を確保する必要がある。

## Decision Drivers / 要求

- Extensibility: ユーザーが独自の `TaskDB` 実装を注入 (Inject) できるようにする。
- Backward Compatibility / DX: 従来通りファイルパス（文字列）を渡すだけの初期化もサポートし、ライトユーザーの負担を増やさない。
- Testability: データベース層のモック化を容易にする。

## Considered Options / 検討

- Option 1: 現状維持（SQLiteを内部でハードコード）。
- Option 2: `db_path` は維持しつつ、内部のファクトリで実装を切り替える。
- Option 3: `Project` に DB インスタンスを直接渡す Dependency Injection (DI) パターン。

## Decision Outcome / 決定

Chosen option: Option 3.

1. 抽象化: `src/beautyspot/db.py` に抽象基底クラス `TaskDB` を定義し、インターフェース（`save`, `get`, `init_schema` 等）を強制する。
2. 具象化: 従来のSQLite実装を `SQLiteTaskDB` として再定義する。
3. 注入 (DI): `Project` クラスのコンストラクタ引数を `db_path: str` から `db: Union[str, TaskDB]` に変更する。
   - `str` が渡された場合: 内部で `SQLiteTaskDB(path)` を生成する（Convenience）。
   - `TaskDB` が渡された場合: そのインスタンスをそのまま使用する（Injection）。

## Consequences / 決定

- Positive:
  - PostgreSQL や MySQL などのアダプタをユーザー側で実装し、`Project(..., db=PostgresDB(...))` のように利用可能になる。
  - テスト時に `MemoryTaskDB` などを差し込むことで、高速かつクリーンなテストが可能になる。
- Negative:
  - `db_path` 引数が廃止（または `db` に統合）されるため、既存コードの引数名変更が必要になる（v1.0.0 での破壊的変更）。

# Database Dependency Injection and Abstraction ## Context and Problem Statement / コンテキスト これまでの `beautyspot` (`v0.x`) は、`Project` クラス内部で `TaskDB` (SQLite実装) をハードコードしてインスタンス化していた。 ```python # v0.x implementation self.db = TaskDB(self.db_path) ``` この設計には以下の課題がある： 1. **拡張性の欠如:** SQLite 以外のデータベース（PostgreSQL, DuckDB, In-Memory DB等）を使いたくても、ライブラリのコードを書き換えない限り不可能である。 2. **テストの制約:** ユニットテスト時に、ファイルシステムに依存しないモックDBやオンメモリDBへの差し替えが困難である。 v1.0.0 では、ユーザー体験（DX）としての「手軽さ（パスを指定するだけ）」を維持しつつ、アーキテクチャレベルでの柔軟性を確保する必要がある。 ## Decision Drivers / 要求 * **Extensibility:** ユーザーが独自の `TaskDB` 実装を注入 (Inject) できるようにする。 * **Backward Compatibility / DX:** 従来通りファイルパス（文字列）を渡すだけの初期化もサポートし、ライトユーザーの負担を増やさない。 * **Testability:** データベース層のモック化を容易にする。 ## Considered Options / 検討 * **Option 1**: 現状維持（SQLiteを内部でハードコード）。 * **Option 2**: `db_path` は維持しつつ、内部のファクトリで実装を切り替える。 * **Option 3**: `Project` に DB インスタンスを直接渡す Dependency Injection (DI) パターン。 ## Decision Outcome / 決定 Chosen option: **Option 3**. 1. **抽象化:** `src/beautyspot/db.py` に抽象基底クラス `TaskDB` を定義し、インターフェース（`save`, `get`, `init_schema` 等）を強制する。 2. **具象化:** 従来のSQLite実装を `SQLiteTaskDB` として再定義する。 3. **注入 (DI):** `Project` クラスのコンストラクタ引数を `db_path: str` から `db: Union[str, TaskDB]` に変更する。 * `str` が渡された場合: 内部で `SQLiteTaskDB(path)` を生成する（Convenience）。 * `TaskDB` が渡された場合: そのインスタンスをそのまま使用する（Injection）。 ## Consequences / 決定 * **Positive:** * PostgreSQL や MySQL などのアダプタをユーザー側で実装し、`Project(..., db=PostgresDB(...))` のように利用可能になる。 * テスト時に `MemoryTaskDB` などを差し込むことで、高速かつクリーンなテストが可能になる。 * **Negative:** * `db_path` 引数が廃止（または `db` に統合）されるため、既存コードの引数名変更が必要になる（v1.0.0 での破壊的変更）。

親: REQ012

子: —

### ADR026 ADR {h(g)} ✓ レビュー済

# Factory Function for Default Dependency Injection

## Context and Problem Statement / コンテキスト

v2.0 以降、`beautyspot` は依存性注入 (DI) アーキテクチャを採用しました。`Spot` クラス（`core.py`）の初期化には、`TaskDB`, `Serializer`, `Storage` の各インスタンスを明示的に渡す必要があります。

これはテスト容易性と柔軟性の観点では優れていますが、単に「すぐに使い始めたい」だけのユーザーにとっては、初期化が非常に冗長になってしまうという課題がありました。

    # 一般的なスクリプトには冗長すぎる
    db = SQLiteTaskDB(...)
    storage = LocalStorage(...)
    serializer = MsgpackSerializer()
    spot = Spot(name="app", db=db, storage=storage, serializer=serializer)

## Decision Drivers / 要求

- Developer Experience (DX): ライトユーザーが最小限の設定（名前のみなど）で即座に開始できること。
- Clean Architecture: `core.py` の `Spot` クラスを具象クラス（SQLite 等）の詳細から保護し、ピュアな状態に保つこと。
- Flexibility: 高度なユーザーが引き続き任意のコンポーネントを注入できる余地を残すこと。

## Considered Options / 検討

- Option 1: ユーザーに常に全ての DI コンポーネントを手動で提供させる。
- Option 2: `Spot.__init__` の引数にデフォルト値（具象クラス）を設定する。
- Option 3: `__init__.py` でファクトリ関数 `Spot` を提供し、そこでデフォルトの組み立てを行う。

## Decision Outcome / 決定

Chosen option: Option 3.

`beautyspot/__init__.py` において、具象クラスのインスタンス化を伴うファクトリ関数 `Spot` を公開します。

    # beautyspot/__init__.py
    def Spot(name: str, db=None, ...):
        resolved_db = db or SQLiteTaskDB(...)
        # ... 他の解決ロジック ...
        return _Spot(name, db=resolved_db, ...)

これにより、ライブラリのトップレベルからインポートされる `Spot` は関数となり、内部で `core.py` の `_Spot` クラスを組み立てて返します。

## Consequences / 決定

- Positive:
  - "Opinionated Defaults, Flexible Internals": 初心者は1行で初期化でき、上級者はカスタムコンポーネントを注入できる。
  - コアとなる `_Spot` クラスが、`SQLiteTaskDB` などの具象実装をインポートする必要がなくなる（関心の分離）。
- Negative:
  - 公開シンボル `Spot` がクラスではなく関数になるため、`Spot` を直接継承することが難しくなる。継承が必要な場合は `core` から `_Spot` をインポートする必要があるが、本ライブラリでは継承よりもコンポジションを推奨するため、許容範囲とする。

# Factory Function for Default Dependency Injection ## Context and Problem Statement / コンテキスト v2.0 以降、`beautyspot` は依存性注入 (DI) アーキテクチャを採用しました。`Spot` クラス（`core.py`）の初期化には、`TaskDB`, `Serializer`, `Storage` の各インスタンスを明示的に渡す必要があります。 これはテスト容易性と柔軟性の観点では優れていますが、単に「すぐに使い始めたい」だけのユーザーにとっては、初期化が非常に冗長になってしまうという課題がありました。 ```python # 一般的なスクリプトには冗長すぎる db = SQLiteTaskDB(...) storage = LocalStorage(...) serializer = MsgpackSerializer() spot = Spot(name="app", db=db, storage=storage, serializer=serializer) ``` ## Decision Drivers / 要求 * **Developer Experience (DX)**: ライトユーザーが最小限の設定（名前のみなど）で即座に開始できること。 * **Clean Architecture**: `core.py` の `Spot` クラスを具象クラス（SQLite 等）の詳細から保護し、ピュアな状態に保つこと。 * **Flexibility**: 高度なユーザーが引き続き任意のコンポーネントを注入できる余地を残すこと。 ## Considered Options / 検討 * **Option 1**: ユーザーに常に全ての DI コンポーネントを手動で提供させる。 * **Option 2**: `Spot.__init__` の引数にデフォルト値（具象クラス）を設定する。 * **Option 3**: `__init__.py` でファクトリ関数 `Spot` を提供し、そこでデフォルトの組み立てを行う。 ## Decision Outcome / 決定 Chosen option: **Option 3**. `beautyspot/__init__.py` において、具象クラスのインスタンス化を伴う**ファクトリ関数 `Spot`** を公開します。 ```python # beautyspot/__init__.py def Spot(name: str, db=None, ...): resolved_db = db or SQLiteTaskDB(...) # ... 他の解決ロジック ... return _Spot(name, db=resolved_db, ...) ``` これにより、ライブラリのトップレベルからインポートされる `Spot` は関数となり、内部で `core.py` の `_Spot` クラスを組み立てて返します。 ## Consequences / 決定 * **Positive**: * **"Opinionated Defaults, Flexible Internals"**: 初心者は1行で初期化でき、上級者はカスタムコンポーネントを注入できる。 * コアとなる `_Spot` クラスが、`SQLiteTaskDB` などの具象実装をインポートする必要がなくなる（関心の分離）。 * **Negative**: * 公開シンボル `Spot` がクラスではなく関数になるため、`Spot` を直接継承することが難しくなる。継承が必要な場合は `core` から `_Spot` をインポートする必要があるが、本ライブラリでは継承よりもコンポジションを推奨するため、許容範囲とする。

親: REQ012

子: —