コンテンツにスキップ

概念

このページでは Configlue を正しく推論するための公開メンタルモデルを説明します。解説が目的であり、チュートリアルではありません。初めて実行する手順ははじめる、目的別の手順はガイドを見てください。

アプリケーション寄りの概念から低い層へ順に説明します。通常のアプリケーションコードは前半だけ理解すれば十分です。

型付き state がアプリケーション境界である

Section titled “型付き state がアプリケーション境界である”

アプリケーションコードが依存するのは次のどちらかです。

  • IReadOnlyState<T> — 現在の設定値を読み取り、変更を観測する。
  • IWritableState<T> — 上記に加えて、疎な変更を保存できる。

storage、serialization、層構成、migration、validation、encryption、hosting integration が変わっても、通常の consumer コードは変わりません。

var current = await settings.GetValueAsync();
await settings.SaveAsync(patch =>
{
patch.Theme = "Dark";
});

読むだけのメソッドは IReadOnlyState<T> を受け、編集するメソッドは IWritableState<T> を受けます。この使い分けが、Configlue がアプリケーションコードに求める主な境界です。

複数の source が部分的な state を寄与できます。Configlue はそれらを解決し、アプリケーションが見る単一の型付き値(effective value)にします。

典型的な層構成はローカル設定+環境変数 override です。

config.UseCommonSources(sources =>
{
sources.WithLocal("settings.json");
sources.WithEnvironment("APP_");
sources.Add<AppSettings>();
});

ここで重要な用語は 2 つです。

  • effective value: すべての寄与をマージした後に GetValueAsync() が返す解決済みの T。
  • provenance: 各値が どこから来たか の記録。どの寄与が勝ったか、編集可能かを含みます。

1 つにマージされたモデルを見た後も「値がどこから来たか」は重要です。画面の値がローカルファイルと違うとき、原因はほぼ常に優先度の高い寄与です。調べ方はガイド、解決の契約はリファレンスを見てください。

読み取りと書き込みは対称ではない

Section titled “読み取りと書き込みは対称ではない”

effective な読み取り値の解決と、変更の保存先の選択は別の関心事です。

  • 読み取りは全層の寄与をマージして 1 つの effective value にします。
  • 書き込みは、読んだ値の一部が他所から来ていても、構成された書き込み可能な寄与(例: ローカルファイル)に向かいます。

つまり「読み取りで勝つ source」と「変更が保存される場所」は設計上異なることがあります。読み取り専用の上位層(管理 policy や環境変数など)が読み取りではローカルファイルを覆い隠しつつ、保存はローカルファイルに向かう構成がありえます。要求された effective value を実現できない保存は、勝っている層を黙って上書きせず conflict として失敗します。

Configlue がモデル全体を雑にシリアライズせず、生成された fragment/patch を通して保存する理由は、全体シリアライズでは次を区別できないからです。

  • absent / missing: ここでは意見を持たないため、下位層が透けて見える。
  • 明示値: この層がメンバーを設定する。
  • 明示 null(メンバー型が許す場合): この層が null を設定する。absent とは異なる。
  • unset / 削除: この層が以前設定した値を取り消す。

上記のローカルファイル+環境変数の層構成を例にします。ファイルが Theme = "Dark" で APP__Theme が未設定なら、上位層のそのメンバーは absent のため読み取りは "Dark" にフォールスルーします。APP__Theme=Light を設定すると上位層が明示的に存在するため、ファイルが "Dark" のままでも同じ読み取りは "Light" を返します。保存も同様です。SaveAsync(patch => ...) は patch 内で設定したメンバーのみを送るため、触れていないメンバーは defaults で上書きされず層の動作を保ちます。

コード上は、absent は fragment/patch でメンバーが設定されていないこと(Optional<T>.Missing)であり、明示的に設定した null とは異なります。Unset() はその層の既存の寄与を取り消します。層の動作に驚いたらこの区別を思い出してください。正確な契約はリファレンスにあります。Configlue の動作を超えた fragment 代数が必要な場合は、ここで重複させずスタンドアロンの SparseFragments ドキュメントを見てください。

Configlue モデルは安定した identity と version メタデータを持ちます(例: [ConfiglueModel("sample.settings", Version = 1)])。version は保存ドキュメント(JSON の $version など)と一緒に運ばれるため、古い schema を黙って誤読せず認識・移行できます。

概念的には、migration はモデル変更後も既存の保存設定を使い続けられるようにし、validation はモデルの規則に反する書き込みを拒否します。どちらも 1 回構成するだけであり、読み書きコードは変わりません。手順はガイド、正確な保証はリファレンスにあります。

state はローカル書き込みでも外部 source の変化(別プロセスによる監視中ローカルファイルの編集など)でも変わります。consumer は storage をポーリングするのではなく effective state を観測します。

using var _ = settings.OnChange(changed =>
{
// effective value が実際に変わったときに実行される。
});

変化した寄与が覆い隠されて effective value が変わらない source リロードでは listener は発火しません。リファレンスに書かれた保証だけに依存してください。内部のポーリングや debounce タイミングは公開契約ではありません。

低レベルのシリアライズ構成(上級者向け)

Section titled “低レベルのシリアライズ構成(上級者向け)”

通常のアプリケーションコードはこの層を扱いません。provider や拡張の作者はシリアライズ系 source を次の経路で組み立てます。

Resource → 任意の Transformers → Codec → 型付き Source capabilities
  • Resource: バイト列の置き場所(ファイル、browser storage、database、cloud)。
  • Transformer: encryption や compression などの任意のバイト列ラップ。
  • Codec: バイト列を構造化データにする方法(JSON、YAML、MessagePack、XML)。
  • Source: state 解決に公開される型付き capabilities(読み取り可、書き込み可、観測可など)。

すべての integration がこの直線パイプラインに収まるわけではありません(HTTP policy や command-line source などはファイル的ではないため)。普遍法則ではなく シリアライズ系 の正準経路として扱ってください。観測可能な動作から出発し、この語彙は provider の作成や診断のときにだけ使い、パッケージ探しはインテグレーションを使ってください。