← 全体レポートに戻る

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

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

対象アイテム

16

REQ

1

ARCH

1

SPEC

2

TST

2

IMPL

2

ADR

8

レビュー済

16/16

Suspect

0

グループ

SERIAL
アイテム: REQ005 ARCH005 SPEC012 SPEC013 TST012 TST013 IMPL012 IMPL013 ADR004 ADR008 ADR014 ADR017 ADR019 ADR025 ADR039 ADR042

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

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

グループREQARCHSPECTSTIMPLADR
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC012
## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP...
TST012
## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン...
IMPL012
## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ...
ADR004
# Resilient Deserialization and Explicit Versioning ## Context and Problem Stat...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC013
## インターフェース ```python def register( code: int, encoder: Callable[[Any],...
TST013
## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ...
IMPL013
## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)...
ADR004
# Resilient Deserialization and Explicit Versioning ## Context and Problem Stat...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC012
## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP...
TST012
## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン...
IMPL012
## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ...
ADR008
# Customizable Serialization Strategy with Msgpack Default ## Context and Probl...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC012
## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP...
TST012
## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン...
IMPL012
## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ...
ADR014
# Decorator-based Type Registration with Late Binding ## Context and Problem St...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC012
## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP...
TST012
## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン...
IMPL012
## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ...
ADR017
title: Nested Msgpack Protocol via Serializer Wrapping status: Accepted date: 20...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC012
## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP...
TST012
## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン...
IMPL012
## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ...
ADR019
# Per-Task Serializer Override ## Context and Problem Statement / コンテキスト プロジェク...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC012
## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP...
TST012
## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン...
IMPL012
## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ...
ADR025
# Protocol-based Serializer Registration ## Context and Problem Statement / コンテ...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC012
## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP...
TST012
## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン...
IMPL012
## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ...
ADR039
# Bounded LRU Cache for Subclass Resolution in Serializer ## Context and Proble...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC012
## インターフェース ```python class MsgpackSerializer(SerializerProtocol, TypeRegistryP...
TST012
## 目的 `MsgpackSerializer` はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレン...
IMPL012
## 実装概要 `MsgpackSerializer` クラスが `SerializerProtocol` と `TypeRegistryProtocol` ...
ADR042
# Thread-safe LRU Cache for Serializer ## Context and Problem Statement / コンテキス...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC013
## インターフェース ```python def register( code: int, encoder: Callable[[Any],...
TST013
## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ...
IMPL013
## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)...
ADR008
# Customizable Serialization Strategy with Msgpack Default ## Context and Probl...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC013
## インターフェース ```python def register( code: int, encoder: Callable[[Any],...
TST013
## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ...
IMPL013
## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)...
ADR014
# Decorator-based Type Registration with Late Binding ## Context and Problem St...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC013
## インターフェース ```python def register( code: int, encoder: Callable[[Any],...
TST013
## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ...
IMPL013
## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)...
ADR017
title: Nested Msgpack Protocol via Serializer Wrapping status: Accepted date: 20...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC013
## インターフェース ```python def register( code: int, encoder: Callable[[Any],...
TST013
## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ...
IMPL013
## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)...
ADR019
# Per-Task Serializer Override ## Context and Problem Statement / コンテキスト プロジェク...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC013
## インターフェース ```python def register( code: int, encoder: Callable[[Any],...
TST013
## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ...
IMPL013
## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)...
ADR025
# Protocol-based Serializer Registration ## Context and Problem Statement / コンテ...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC013
## インターフェース ```python def register( code: int, encoder: Callable[[Any],...
TST013
## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ...
IMPL013
## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)...
ADR039
# Bounded LRU Cache for Subclass Resolution in Serializer ## Context and Proble...
SERIALREQ005
関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。
ARCH005
## コンポーネント構成 ```mermaid graph TD A[MsgpackSerializer] -->|Registry| B[TypeReg...
SPEC013
## インターフェース ```python def register( code: int, encoder: Callable[[Any],...
TST013
## 目的 カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は `SerializationError` によるキャッ...
IMPL013
## 実装概要 2つのレイヤーで構成: - `MsgpackSerializer.register(type, code, encoder, decoder)...
ADR042
# Thread-safe LRU Cache for Serializer ## Context and Problem Statement / コンテキス...

カバレッジ(局所)

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

アイテム詳細

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

関数の戻り値を安全にシリアライズ・デシリアライズできること。ユーザー定義型のカスタムエンコーダ・デコーダを登録可能であること。

親:

子: ADR004, ADR008, ADR014, ADR017, ADR019, ADR025, ADR039, ADR042, ARCH005

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

コンポーネント構成

graph TD
  A[MsgpackSerializer] -->|Registry| B[TypeRegistryProtocol]
  A -->|LRU Cache| C[Thread-Local Storage]
  A -->|Core| D[msgpack-python]

コンポーネント責務

コンポーネント 責務 インターフェース
MsgpackSerializer MessagePackベースの高速・コンパクトなバイナリ変換 dumps(), loads()
TypeRegistryProtocol ユーザー定義型(ExtType)のエンコーダ・デコーダ登録 register()
Thread-Local Cache free-threading環境でのロック競合を回避するMRO解決キャッシュ OrderedDict (LRU)

データフロー

sequenceDiagram
  participant User as ユーザーコード
  participant Ser as MsgpackSerializer
  participant Reg as TypeRegistry
  participant MP as msgpack

  User->>Ser: dumps(obj)
  Ser->>Ser: 型チェック & MRO解決
  opt カスタム型
    Ser->>Reg: エンコーダ取得
    Reg-->>Ser: encoder_fn
    Ser->>MP: ExtType(code, encoder_fn(obj))
  end
  MP-->>Ser: binary
  Ser-->>User: bytes

技術選定

技術領域 選定 理由
フォーマット MessagePack JSONより高速・コンパクト。バイナリをネイティブにサポート
スレッド安全 Copy-on-Write (CoW) 読み取りパスからロックを排除し、マルチスレッド性能を最大化
型拡張 ExtType (0-127) カスタム型を1バイトのオーバーヘッドで表現可能

非機能要件方針

親: REQ005

子: SPEC012, SPEC013

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

インターフェース

class MsgpackSerializer(SerializerProtocol, TypeRegistryProtocol):
    def dumps(self, obj: Any) -> bytes: ...
    def loads(self, data: bytes) -> Any: ...

振る舞い

シリアライズ (dumps)

  1. オブジェクトの型を type(obj) で取得
  2. 登録済み型またはそのサブクラス(MRO順)を探索
  3. ヒットした場合、カスタムエンコーダを実行し ExtType としてパック
  4. ヒットしない場合、MessagePack のデフォルト処理(または SerializationError

デシリアライズ (loads)

  1. MessagePack ストリームをパース
  2. ExtType を発見した場合、コードに対応するカスタムデコーダを呼び出す

パラメータ詳細

設定 デフォルト 説明
max_cache_size 1024 スレッドごとの型解決LRUキャッシュの最大サイズ

エラーハンドリング

エッジケース

親: ARCH005

子: IMPL012, TST012

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

インターフェース

def register(
    code: int,
    encoder: Callable[[Any], Any],
    decoder: Callable[[Any], Any],
) -> Callable: ... # デコレータ形式

def register_type(
    type_class: Type,
    code: int,
    encoder: Callable[[Any], Any],
    decoder: Callable[[Any], Any],
) -> None: ...

振る舞い

  1. 重複チェック: 同一コードまたは同一クラスが既に登録されていないか確認
  2. CoW更新: 現在のレジストリ辞書をコピーし、新しいエントリを追加した後に参照を差し替える
  3. 世代更新: _registry_generation をインクリメントし、全スレッドのLRUキャッシュを無効化対象とする

パラメータ詳細

パラメータ 制限 説明
code 0127 MessagePack ExtType のユーザー定義領域コード
encoder 引数1、戻り値シリアライズ可能 オブジェクトをプリミティブ構造へ変換する関数
decoder 引数1、戻り値オブジェクト プリミティブ構造からオブジェクトを復元する関数

エラーハンドリング

エッジケース

親: ARCH005

子: IMPL013, TST013

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

目的

MsgpackSerializer はキャッシュデータの永続化形式を決定する。シリアライズの不具合はデータ消失(デシリアライズ不能)やサイレントなデータ劣化(精度低下等)に直結する。スレッドセーフ性の欠如はマルチスレッド環境での競合状態を引き起こす。

検証観点

references: tests/unit/test_serializer.py

親: SPEC012

子:

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

目的

カスタム型登録はユーザー定義クラスのキャッシュを可能にする拡張ポイントである。登録の不備は SerializationError によるキャッシュ保存失敗を引き起こし、ユーザーの既存コードとの統合を阻害する。デコレータ方式と命令方式の両方の登録パスを検証する。

検証観点

references: tests/unit/test_type_registry.py

親: SPEC013

子:

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

実装概要

MsgpackSerializer クラスが SerializerProtocolTypeRegistryProtocol を実装。 dumps(obj) -> bytes / loads(data) -> Any が主要 API。 カスタム型は msgpack の ExtType(code, payload) で拡張する。 _default_packer() がエンコード時のカスタム型ディスパッチ、 _ext_hook() がデコード時の型復元を行う。

設計判断

Copy-on-Write レジストリ

_encoders / _decoders 辞書は register() 時に新しい辞書を作成して アトミックに差し替える(CoW)。読み取り側はスナップショットを参照するため、 ロックなしで安全に読める。GIL に依存せず、PEP 703(free-threading)にも 対応できる設計。世代カウンタ _registry_generationレジストリ差し替えの前に インクリメントすることで、読み取り側が古いキャッシュを使い続けることを防ぐ。

スレッドローカル LRU キャッシュ

MRO スキャンの結果(サブクラス→登録済み親クラスの解決)を threading.local()OrderedDict にキャッシュする。_cache_generation_registry_generation を比較し、 世代が変わったらキャッシュを破棄する。LRU のサイズ上限は max_cache_size で制御。

MRO スキャンによるサブクラス自動解決

Pydantic モデルのサブクラスのように、親クラスが登録済みなら サブクラスも自動的に同じエンコーダで処理できる。type(obj).__mro__ を走査し、 最初にマッチした登録型のエンコーダを使用する。

実装メモ

references: src/beautyspot/serializer.py

親: SPEC012

子:

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

実装概要

2つのレイヤーで構成: - MsgpackSerializer.register(type, code, encoder, decoder): 低レベルの型登録 API - Spot.register() / Spot.register_type(): ユーザー向けの高レベル API

Spot.register() はデコレータ形式、Spot.register_type() は命令形式で、 いずれも内部で serializer.register() に委譲する(TypeRegistryProtocol 準拠時のみ)。

設計判断

二層 API の提供

デコレータ形式 @spot.register(code=1, ...) はクラス定義と同時に登録でき、 宣言的で読みやすい。命令形式 spot.register_type(MyClass, code=1, ...) は 外部ライブラリのクラスなどデコレートできない場合に使用する。

TypeRegistryProtocol による条件分岐

シリアライザが TypeRegistryProtocol を実装していない場合(カスタムシリアライザ)、 register() / register_type()ConfigurationError を送出する。 これにより、型登録非対応のシリアライザを使用する場合のエラーメッセージが明確になる。

実装メモ

references: src/beautyspot/serializer.py, src/beautyspot/core.py

親: SPEC013

子:

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

Resilient Deserialization and Explicit Versioning

Context and Problem Statement / コンテキスト

Pythonの pickle は、保存されたオブジェクトのクラス定義と現在のコードが一致していることを前提とします。 開発中はクラス定義が頻繁に変更されるため、過去のキャッシュを読み込む際に AttributeError 等が発生し、アプリケーションがクラッシュする問題がありました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

  1. Fail Safe (Auto Recalc): Storage.load() でデシリアライズエラー(クラス不整合や破損)が発生した場合、これを CacheCorruptedError として捕捉し、core.py 側で 「キャッシュミス」 として扱う。これによりアプリをクラッシュさせず、自動的に再計算を行う。
  2. Explicit Versioning: ユーザーが意図的にキャッシュを無効化できるよう、@task デコレータに version 引数を追加する。これを変更するとキャッシュキーが変わり、古い(互換性のない)キャッシュを参照しなくなる。
  3. User Guidance: 自動再計算が発生した際、ログに「コード変更時は version の更新を検討してね」というヒントを出力し、ベストプラクティスへ誘導する。

Consequences / 決定

親: REQ005

子:

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

Customizable Serialization Strategy with Msgpack Default

Context and Problem Statement / コンテキスト

現在、beautyspot はキャッシュデータのシリアライズ(保存)に Python 標準の pickle を使用している。 これには以下の重大な課題がある:

  1. セキュリティリスク (RCE): pickle は信頼できないデータを読み込む際に任意のコード実行(RCE)を引き起こす可能性があり、「共有キャッシュ」としての利用にリスクがある。
  2. 互換性: Python のバージョンやライブラリのバージョンが変わると、クラス定義の不整合によりデシリアライズに失敗しやすい。
  3. 代替案の欠点: 標準ライブラリの json は安全だが、画像などのバイナリデータを効率的に扱えず(Base64化でサイズ増)、タプルなどのPython固有型が失われる。

v1.0.0 リリースにあたり、「デフォルトで安全(Secure by Default)」 かつ 「拡張性(Extensibility)」 のあるシリアライズ戦略が必要である。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 4.

シリアライザのバックエンドとして msgpack (MessagePack) を採用し、さらにユーザーが任意の型変換ロジックを注入できる TypeRegistry パターンを導入する。

  1. 依存関係: msgpack>=1.0.0pyproject.toml に追加する。
  2. デフォルトの挙動:
    • pickle はデフォルトから廃止(またはオプトイン化)する。
    • msgpack を使用し、安全な型のみをシリアライズする。
  3. カスタム型の登録:
    • Project クラスに register_type(type, code, encoder, decoder) メソッドを追加する。
    • ユーザーは、シリアライズできない型(例: カスタムクラス)に対して、一意なID (code) と変換関数を登録することで対応できる。
  4. エラーハンドリング:
    • 未登録の型に遭遇した場合、標準の TypeError ではなく、具体的なオブジェクト情報と register_type の利用を促す親切なエラーメッセージ (SerializationError) を送出する。

Technical Details / 技術詳細

msgpackdefault (pack時) と ext_hook (unpack時) を活用し、拡張型を msgpack.ExtType としてラップする。

# Implementation Sketch

class Project:
    def __init__(self, ...):
        # ...
        self.serializer = MsgpackSerializer()

    def register_type(self, type_: type, code: int, encoder: Callable, decoder: Callable):
        """
        Register a custom serializer for a specific type.

        Args:
            type_: The class to handle (e.g. MyClass)
            code: Unique integer ID (0-127) for this type
            encoder: Function that converts obj -> bytes
            decoder: Function that converts bytes -> obj
        """
        self.serializer.register(type_, code, encoder, decoder)

# --- Internal ---

class MsgpackSerializer:
    def dump(self, obj):
        return msgpack.packb(obj, default=self._default_packer, use_bin_type=True)

    def load(self, data):
        return msgpack.unpackb(data, ext_hook=self._ext_hook, raw=False)

    def _default_packer(self, obj):
        # 登録済みの型なら ExtType に変換
        if type(obj) in self._encoders:
            code, encoder = self._encoders[type(obj)]
            return msgpack.ExtType(code, encoder(obj))

        # 未登録なら詳細なエラーを出す
        raise SerializationError(f"Object of type '{type(obj).__name__}' is not serializable...")

Consequences / 決定

Updates (2025-12-11)

See ADR-0009 for further refinements regarding save_blob=False behavior (Msgpack + Base64) and size guardrails.

親: REQ005

子:

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

Decorator-based Type Registration with Late Binding

Context and Problem Statement / コンテキスト

これまでの spot.register_type() は命令的であり、クラス定義と登録ロジックが分離してしまうため、凝集度が低いという課題がありました。 また、Pydantic モデルのようなクラスメソッド(例: cls.model_validate_json)をデコーダとして登録したい場合、デコレータ評価時にはまだクラスが未定義(NameError)であるため、綺麗に記述できないという技術的な制約がありました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

Spot クラスに register デコレータメソッドを追加し、decoder_factory による遅延バインディング を導入します。

Technical Details / 技術詳細

@spot.register(
    code=10,
    encoder=lambda obj: ...,
    decoder_factory=lambda cls: cls.deserialize  # Class is passed here after definition
)
class MyModel:
    ...
  1. register デコレータは、対象クラスの定義完了後(デコレート時)に実行される。
  2. decoder_factory が指定されている場合、生成された cls オブジェクトを引数としてファクトリを実行し、実際の decoder 関数を取得する。
  3. 取得した decoder を用いて、既存の register_type バックエンドに登録する。

Consequences / 決定

親: REQ005

子:

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

title: Nested Msgpack Protocol via Serializer Wrapping status: Accepted date: 2026-02-02 context: Improving DX for Custom Types


Nested Msgpack Protocol via Serializer Wrapping

Context and Problem Statement / コンテキスト

これまでは、カスタム型のエンコーダは ExtType の仕様に合わせて「生のバイト列」を返す必要がありました。 これにより、ユーザーは msgpack ライブラリを直接操作する必要があり、Pydantic モデルなどの扱いが煩雑になっていました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

Serializer Wrapping (Nested Protocol) を採用します。

  1. MsgpackSerializer は、ユーザー定義のエンコーダ/デコーダに対するラッパー(Wrapper)として機能する。
  2. Encode時: エンコーダが返したオブジェクト(中間表現)を、ライブラリが自動的に packb して ExtType に格納する。
  3. Decode時: ExtType のデータを、ライブラリが自動的に unpackb してからデコーダに渡す。
  4. これにより、ユーザーは import msgpack をする必要がなくなり、直感的な実装が可能になる。

Consequences / 決定

親: REQ005

子:

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

Per-Task Serializer Override

Context and Problem Statement / コンテキスト

プロジェクト全体としては安全性と互換性の観点から msgpack を標準シリアライザとしています。 しかし、探索的データ分析(EDA)やプロトタイピング、あるいは msgpack でのシリアライズが困難なサードパーティ製オブジェクトを扱う特定のタスクにおいては、Python標準の pickle のような柔軟なシリアライザを局所的に使用したいというニーズがあります。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

  1. Override Mechanism: Spot.mark() および Spot.cached_run() に、オプショナル引数 serializer を追加します。
  2. Protocol: ユーザーは dumps(obj) -> bytes および loads(bytes) -> obj メソッドを持つ任意のオブジェクトを渡すことができます。
  3. Fallback Behavior: 指定されたシリアライザでデシリアライズに失敗した場合、ADR-0003 の方針に従い、キャッシュ破損として扱い、タスクを再実行します。

Consequences / 決定

親: REQ005

子:

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

Protocol-based Serializer Registration

Context and Problem Statement / コンテキスト

beautyspot では、@spot.register デコレータを使用してカスタム型を登録できます。 当初の実装では、Spot クラス内でシリアライザが MsgpackSerializer のインスタンスであるかどうかを明示的にチェックしていました。

if isinstance(self.serializer, MsgpackSerializer):
    self.serializer.register(...)

これは、高レベルの Spot クラスを特定の具象クラス (MsgpackSerializer) に結合させており、依存関係逆転の原則 (Dependency Inversion Principle) に違反していました。その結果、カスタム型登録をサポートする他のシリアライザ(将来的な JsonSerializer やカスタムラッパーなど)を使用することが不可能でした。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

beautyspot.serializerTypeRegistryProtocol を導入します。

  1. Define Protocol: 型登録に必要な register メソッドのシグネチャを規定するプロトコルを定義します。
  2. Decoupling: Spot クラスは、具象クラスではなくこのプロトコルに対して適合性チェックを行うように変更します。
  3. Interface Segregation: 基本的な SerializerProtocol (dump/load) と TypeRegistryProtocol を分離し、型登録をサポートしないシリアライザも許容できるようにします。

Consequences / 決定

親: REQ005

子:

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

Bounded LRU Cache for Subclass Resolution in Serializer

Context and Problem Statement / コンテキスト

MsgpackSerializer_default_packer において、クラスの解決結果を保存するキャッシュ辞書が、動的に型が生成される環境(動的な Pydantic モデル等)で無制限に肥大化し、メモリリークを引き起こす懸念がありました。 また、この辞書へのアクセスは非常に高頻度であるため、厳密なスレッドセーフティ(ロック)を導入すると、シリアライズ性能のボトルネック(ロック競合)を招く恐れがありました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 3.

標準ライブラリの collections.OrderedDict を利用し、上限サイズ(デフォルト 1024)を持つ LRU (Least Recently Used) キャッシュ を導入します。マルチスレッド環境下でのアクセスに対しては、意図的にロックフリーなアプローチを維持します。

Consequences / 決定

親: REQ005

子:

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

Thread-safe LRU Cache for Serializer

Context and Problem Statement / コンテキスト

MsgpackSerializer は動的型生成時のメモリリークを防ぐために、内部で OrderedDict を用いた LRU キャッシュを運用しています。しかし、バックグラウンド保存や Web フレームワークでの利用など、マルチスレッド環境からの並行アクセスが日常的に発生します。ロックを持たない OrderedDict への並行操作は、RuntimeError やデータの破損を引き起こす致命的なバグの温床となっていました。

Decision Drivers / 要求

Considered Options / 検討

Decision Outcome / 決定

Chosen option: Option 2.

MsgpackSerializer の内部状態(キャッシュおよびレジストリ)に対するすべての読み書き操作を threading.Lock() で保護します。

  1. 安定性の優先: 確実な排他制御により、マルチスレッド下での信頼性を担保します。
  2. 限定的なロック範囲: ロックの範囲を「型の解決とキャッシュの更新」というメモリアクセス領域に限定し、重いシリアライズ処理(I/O や計算)はロック外で実行します。

Consequences / 決定

親: REQ005

子: