エントリレジストリ

登録されたエントリのクエリと変更。メタデータ、スナップショット、バージョン履歴にアクセス。

ロード

local registry = require("registry")

エントリ構造

{
    id = "app.lib:assert",     -- string: "namespace:name"
    kind = "function.lua",     -- string: エントリタイプ
    meta = {type = "test"},    -- table: 検索可能なメタデータ
    data = {...}               -- any: エントリペイロード
}

registry.get、registry.find、snap:entries()、snap:get()、snap:namespace()、snap:find()から読み取られるエントリは、これら4つの作者向けフィールドのみを保持する。

dependency_rootはchanges:create()とchanges:update()が受け付ける書き込み側フィールド。ns.dependencyエントリをデプロイメントルートとしてマークするブール値。エントリAPIから返されることはなく、レジストリ所有の状態はsnap:state()を通じて読み取る。

エントリの取得

local entry, err = registry.get("app.lib:assert")

権限: エントリIDに対するregistry.get

エントリの検索

local entries, err = registry.find({[".kind"] = "function.lua"})
local entries, err = registry.find({[".kind"] = "http.endpoint", [".ns"] = "app.api"})

.で始まるキーはエントリフィールド(.kind、.ns、.name、.id)と照合し、*によるグロブを受け付ける。meta.で始まるキーはエントリメタデータと照合し、meta.キーの先頭に付けた~、*、^、$はそれぞれ正規表現、部分一致、前方一致、後方一致を選択する。どちらのプレフィックスも持たないキーは無視される。

IDの解析

local id = registry.parse_id("app.lib:assert")
-- id.ns = "app.lib", id.name = "assert"

スナップショット

レジストリのポイントインタイムビュー:

local snap, err = registry.snapshot()           -- 現在の状態
local snap, err = registry.snapshot_at(5)       -- バージョン5時点

スナップショットメソッド

メソッド 戻り値 説明
snap:entries() Entry[], error アクセス可能なすべてのエントリ
snap:state() State, error レジストリ所有のメタデータ付きのエントリと、解決されたモジュールグラフ
snap:get(id) Entry, error IDで単一エントリを取得
snap:find(filter) Entry[] エントリをフィルター
snap:namespace(ns) Entry[] 名前空間内のエントリ
snap:version() Version スナップショットバージョン
snap:changes() Changes チェンジセットを作成

スナップショットの状態

snap:state()は、エントリの状態と、そのスナップショットバージョンに対して選択されたモジュールグラフを合わせて返す。レジストリ所有の来歴はmetaにマージされるのではなく各エントリ上に保持されるため、作者が記述したメタデータと混同されることはない。

local snap, err = registry.snapshot()
local state, err = snap:state()

for _, entry in ipairs(state.entries) do
    print(entry.id, entry.registry.owner, entry.registry.root)
end

if state.resolution then
    print(state.resolution.digest, state.resolution.input_digest)
    for _, module in ipairs(state.resolution.modules) do
        print(module.name, module.version)
    end
end

state.entries内の各エントリは、4つの作者向けフィールドに加えて次を持つ:

  • registry.owner - エントリを供給したデプロイメントソース
  • registry.root - エントリがデプロイメントによって選択された依存関係の宣言である場合にtrue

state.resolutionはregistry.snapshot()のビューにおけるモジュールグラフを表す。registry.snapshot_at()やオーバーレイのスナップショットを含め、自身のグラフを持たないスナップショットでは存在しない:

フィールド 型 説明
digest string 不変な選択結果全体のコンテンツダイジェスト
input_digest string 宣言されたルート集合のダイジェスト
baseline_digest string グラフの解決対象となったデプロイメントベースラインのダイジェスト。未束縛の場合は省略
roots array ソルバーの入力として使われた、記述された依存関係の宣言
references array 同一コンポーネントの既存ルートに畳み込まれた、ルート形式の宣言。空の場合は省略
modules array 選択されたモジュール

rootsとreferencesの各要素はid、component、versionを持つ。modulesの各要素はnameとversionを持ち、設定されている場合はさらにversion_id、source、digest、size_bytes、protectedを持つ。

バージョン

local version, err = registry.current_version()
local versions, err = registry.versions()

print(version:id())       -- 数値ID
print(version:string())   -- 表示文字列
local prev = version:previous()  -- 前のバージョンまたはnil
local next = version:next()      -- 次のバージョンまたはnil

履歴

local hist, err = registry.history()
local versions, err = hist:versions()
local version, err = hist:get_version(5)
local snap, err = hist:snapshot_at(version)

チェンジセット

変更を構築して適用:

local snap, err = registry.snapshot()
local changes = snap:changes()

changes:create({
    id = "test:new_entry",
    kind = "test.kind",
    meta = {type = "test"},
    data = {config = "value"}
})

changes:update({
    id = "test:existing",
    kind = "test.kind",
    meta = {updated = true},
    data = {new_value = true}
})

changes:delete("test:old_entry")

local new_version, err = changes:apply()

権限: changes:apply()に対するregistry.apply

エントリの削除

changes:delete()は、ID文字列、id文字列を持つテーブル、nsとnameの文字列を持つテーブル、またはそれらいずれかの配列を受け付ける。配列はネスト可能で、重複したIDは単一のdelete操作にまとめられる。

changes:delete("test:old_entry")
changes:delete({id = "test:old_entry"})
changes:delete({ns = "test", name = "old_entry"})
changes:delete({"test:a", {ns = "test", name = "b"}, {"test:c"}})

空のリスト、自身を参照するテーブル、文字列でもテーブルでもない値はerrors.INVALIDとして拒否される。

Changesメソッド

メソッド 説明
changes:create(entry) create操作を追加
changes:update(entry) update操作を追加
changes:delete(id) delete操作を追加
changes:ops() 保留中の操作を取得
changes:apply() 変更を適用、新しいVersionを返す

バージョンの適用

特定のバージョンにロールバックまたはロールフォワード:

local prev = current_version:previous()
local ok, err = registry.apply_version(prev)

権限: registry.apply_version

デルタの構築

状態間を遷移する操作を計算:

local from = {{id = "test:a", kind = "test", meta = {}, data = {}}}
local to = {{id = "test:b", kind = "test", meta = {}, data = {}}}

local ops, err = registry.build_delta(from, to)
for _, op in ipairs(ops) do
    print(op.kind, op.entry.id)  -- "entry.create", "entry.update", "entry.delete"
end

オーバーレイ

オーバーレイは、論理的なアイデンティティが所有するプロセスローカルなレジストリエントリの集合。オーバーレイエントリは通常のトポロジーとハンドラ遷移に参加するため、サービスは永続エントリとまったく同様に起動・停止するが、レジストリ履歴を進めることはなく、バージョンに現れることもない。実行中のプロセス内にのみ存在し、コールドブート後は空になるため、所有する制御サービスが起動時にこれを整合させる。

local snap, err = registry.overlay("data-sources:crm")

戻り値: Snapshot, error

このスナップショットは通常のメソッドを通じてオーナーのオーバーレイエントリを公開し、snap:version()から現在のレジストリバージョンを報告する。また、開かれた時点のオーバーレイ世代を取得しており、これが書き込みの安全性を担保する。

local snap, err = registry.overlay("data-sources:crm")
if err then return nil, err end

local changes = snap:changes()
changes:create({
    id = "data.crm:connection",
    kind = "registry.entry",
    meta = {},
    data = {endpoint = "https://crm.internal"}
})

local version, err = changes:apply()

オーバーレイスナップショット上のchanges:apply()はオーバーレイに書き込み、現在のレジストリバージョンを返す。履歴バージョンは作成されないため、並行して永続的な変更が起きない限り、返されるバージョンは変わらない。

並行性

各オーバーレイは、適用が成功するたびに増加する世代カウンタを持つ。changes:apply()は、その世代がスナップショットを開いた時点で取得したものと一致する場合にのみ成功する。同じオーバーレイへの並行した適用は、リトライ可能とマークされたerrors.CONFLICTで失敗する。オーバーレイを開き直してチェンジセットを組み立て直すこと。

local last_err
for _ = 1, 3 do
    local snap, err = registry.overlay("data-sources:crm")
    if err then return nil, err end

    local _, apply_err = snap:changes():delete("data.crm:connection"):apply()
    if not apply_err then return true end
    if not apply_err:retryable() then return nil, apply_err end
    last_err = apply_err
end
return nil, last_err

制約

  • オーナー文字列は必須であり、空であってはならない。
  • チェンジセットは空であってはならず、同じエントリを二度指定してはならない。
  • IDが永続状態またはいずれかのオーバーレイにすでに存在する場合、createは失敗する。
  • updateとdeleteは、このオーナーが作成したエントリに対してのみ機能する。それ以外のIDはerrors.NOT_FOUNDで失敗する。
  • オーバーレイエントリはdependency_rootやその他のレジストリ所有のメタデータを設定できない。
  • オーバーレイエントリはns.dependencyのような、レジストリディレクティブが所有する種別を使用できない。
  • 生存する他のエントリが依存しているエントリを取り除く削除は拒否される。
  • 依存関係はオーバーレイのオーナー境界を越えられず、永続エントリはオーバーレイエントリに依存できない。

残りはerrors.CONFLICTまたはerrors.INVALIDとして現れ、いずれもリトライ不可。リトライ可能なのは上記の世代の不一致のみ。

権限: 開いて読み取るにはオーナーに対するregistry.overlay.get、書き込むにはオーナーに対するregistry.overlay.apply、そしてチェンジセット内の各エントリIDに対するregistry.overlay.<create|update|delete>.<kind>。

権限

権限 リソース 説明
registry.get entry ID エントリを読み取り(find/entries結果もフィルター)
registry.apply - チェンジセットを適用
registry.apply_version - バージョンを適用/ロールバック
registry.overlay.get オーナーID オーバーレイスナップショットを開いて読み取り
registry.overlay.apply オーナーID オーバーレイのチェンジセットを適用
registry.overlay.create.<kind> エントリID その種別のオーバーレイエントリを作成
registry.overlay.update.<kind> エントリID その種別のオーバーレイエントリを更新
registry.overlay.delete.<kind> エントリID その種別のオーバーレイエントリを削除

エラー

条件 種別
エントリが見つからない errors.NOT_FOUND
バージョンが見つからない errors.NOT_FOUND
権限拒否 errors.PERMISSION_DENIED
無効なパラメータ errors.INVALID
適用する変更がない errors.INVALID
適用中にオーバーレイが変更された errors.CONFLICT(リトライ可能)
オーバーレイエントリが他所に所有されている、または永続状態と競合する errors.CONFLICT
レジストリが利用不可 errors.INTERNAL

エラーの処理についてはエラー処理を参照。