Skill 档案存储 —— 可插拔后端 + 串行化写入的 GenServer。
自 v0.2 起 Store 不再直接操作 ETS,而是把存储细节委托给
CMDCSkillEngine.Store.Backend。默认后端仍是 ETS(零依赖、适合开发/单元测试),
需要跨重启持久化时可切到 SQLite:
# ETS(默认)
children = [CMDCSkillEngine.Store]
# SQLite
children = [
{CMDCSkillEngine.Store,
backend: CMDCSkillEngine.Store.Backend.SQLite,
backend_opts: [path: "./priv/skills.db"]}
]v0.3 多租户切片(:scope keyword)
所有按 skill_id 查询 / 写入计数的 API 都接收可选 :scope keyword(默认 "global")。
- 单租户部署:完全不传
:scope,行为等价 v0.2.x - SaaS 多租户:每个租户独立 scope(建议传
tenant_id),不同 scope 互不可见
CMDCSkillEngine.Store.list_active() # scope = "global"
CMDCSkillEngine.Store.list_active(scope: "tenant_a") # 仅 tenant_a 的 Skills
CMDCSkillEngine.Store.get_record(id, scope: "tenant_a") # 跨 scope 查不到
CMDCSkillEngine.Store.update_counters(id, [selections: 1], scope: "tenant_a")save_record/1 的 scope 由 record.scope 字段承载,不通过 keyword 显式传入。
如果调用方手工构造 SkillRecord 没设置 scope,默认入 "global"。
责任
- SkillRecord 的 CRUD
- 版本 DAG 查询(按
lineage.parent_skill_ids追溯,限定同 scope 内) - 原子计数器(selections / applied / completions / fallbacks)
- 版本血统 DAG 的本地图遍历(跨后端共用逻辑)
并发语义
- 所有写入通过 GenServer 串行化,保证原子性
- 读取(get_record / list_active / get_version_chain)与写入同走 GenServer,
避免 backend 内部的并发一致性问题;后端实现可以假设任意时刻只有一个
handle_call在执行
Summary
Functions
返回当前挂载的 backend 模块与状态(调试/运维用)。
Returns a specification to start this module under a supervisor.
按 skill_id 取回档案。
批量取回档案,返回只含存在 id 的 map。
返回某个 Skill 的完整版本链:根节点在最前,按 generation 升序。
返回该 scope 下所有 is_active = true 的 SkillRecord,按 name 升序。
清空所有档案,跨 scope 全部清空(测试专用)。
插入/覆盖一条 SkillRecord。
启动 Store GenServer。
原子递增计数器。支持 :selections / :applied / :completions / :fallbacks。
Functions
返回当前挂载的 backend 模块与状态(调试/运维用)。
注意:backend_state 可能包含底层 handle(例如 SQLite conn),
调用方不应直接操作。
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec get_record( String.t(), keyword() ) :: {:ok, CMDCSkillEngine.Types.SkillRecord.t()} | {:error, :not_found}
按 skill_id 取回档案。
选项
:scope— 数据切片维度(默认"global");跨 scope 查询返回:not_found
@spec get_records( [String.t()], keyword() ) :: {:ok, %{required(String.t()) => CMDCSkillEngine.Types.SkillRecord.t()}}
批量取回档案,返回只含存在 id 的 map。
选项
:scope— 数据切片维度(默认"global");所有 id 在同一 scope 下查询
@spec get_version_chain( String.t(), keyword() ) :: {:ok, [CMDCSkillEngine.Types.SkillRecord.t()]}
返回某个 Skill 的完整版本链:根节点在最前,按 generation 升序。
在传入 skill_id 同 scope 内回溯。如果中间缺链,返回能重建的
最长前缀(遇到第一个缺失祖先即停止向上追溯)。
选项
:scope— 数据切片维度(默认"global")
@spec list_active(keyword()) :: {:ok, [CMDCSkillEngine.Types.SkillRecord.t()]}
返回该 scope 下所有 is_active = true 的 SkillRecord,按 name 升序。
选项
:scope— 数据切片维度(默认"global")
@spec reset() :: :ok
清空所有档案,跨 scope 全部清空(测试专用)。
@spec save_record(CMDCSkillEngine.Types.SkillRecord.t()) :: {:ok, CMDCSkillEngine.Types.SkillRecord.t()}
插入/覆盖一条 SkillRecord。
scope 由 record.scope 字段携带(默认 "global"),不通过 keyword。
@spec start_link(keyword()) :: GenServer.on_start()
启动 Store GenServer。
选项
:name— 进程名(默认CMDCSkillEngine.Store):backend—CMDCSkillEngine.Store.Backend实现模块(默认 ETS):backend_opts— 透传给backend.init/1的 keyword
原子递增计数器。支持 :selections / :applied / :completions / :fallbacks。
CMDCSkillEngine.Store.update_counters("sk_123", selections: 1, applied: 1)
CMDCSkillEngine.Store.update_counters("sk_123", [selections: 1], scope: "tenant_a")选项(最后一个参数)
:scope— 数据切片维度(默认"global")