CMDCSkillEngine.Store.Backend behaviour (cmdc_skill_engine v0.3.0)

Copy Markdown View Source

Skill 持久化后端 behaviour。

CMDCSkillEngine.Store 的存储细节从 GenServer 主体里解耦, 让同一个 GenServer 可以在不同场景下挂载不同后端:

v0.3 多租户切片

v0.3 起所有按 skill_id 检索 / 写入计数的 callback 加 scope 参数(String.t())。

  • 单租户私有化部署不变:Store 上层默认 scope = "global"
  • SaaS 多租户:每个租户独立 scope(如 tenant_a_uuid),同一 backend 存储多个租户的数据但相互隔离
  • 不同 scope 下允许出现同名 / 同 skill_id 的记录(互不可见)

Breaking change(v0.3.0 vs v0.2.x):所有 callback 签名加 scope 参数(save_record/2 例外 —— scope 由 record.scope 字段携带)。 第三方 backend 实现需要适配。

约定

  • 所有回调接收/返回 state,由 Store 持有、不可见于外部调用方
  • 回调 不得抛异常,失败返回 {:error, reason}
  • save_record/2 负责 upsert(若 (record.scope, record.skill_id) 已存在则覆盖)
  • update_counters/4 必须原子性保证同一条 record 的四项计数递增是一致的
  • list_all/2 返回该 scope 下全部 record,由 Store 客户端过滤 is_active, 让后端实现尽量简单
  • reset/1 清空所有 scope(仅测试用)

自定义后端

defmodule MyApp.PGBackend do
  @behaviour CMDCSkillEngine.Store.Backend

  @impl true
  def init(opts), do: ...

  @impl true
  def get_record(state, scope, skill_id), do: ...
  # ...
end

然后在 CMDCSkillEngine.Store.start_link/1 传入 backend: MyApp.PGBackend

Summary

Types

scope()

@type scope() :: String.t()

state()

@type state() :: term()

Callbacks

get_record(state, scope, t)

@callback get_record(state(), scope(), String.t()) ::
  {:ok, CMDCSkillEngine.Types.SkillRecord.t()} | {:error, :not_found}

init(keyword)

@callback init(keyword()) :: {:ok, state()} | {:error, term()}

list_all(state, scope)

@callback list_all(state(), scope()) ::
  {:ok, [CMDCSkillEngine.Types.SkillRecord.t()]} | {:error, term()}

reset(state)

@callback reset(state()) :: :ok | {:error, term()}

save_record(state, t)

@callback save_record(state(), CMDCSkillEngine.Types.SkillRecord.t()) ::
  {:ok, CMDCSkillEngine.Types.SkillRecord.t()} | {:error, term()}

terminate(state)

@callback terminate(state()) :: :ok

update_counters(state, scope, t, keyword)

@callback update_counters(state(), scope(), String.t(), keyword()) ::
  :ok | {:error, :not_found} | {:error, term()}