CMDCGateway.Webhook.DeadLetter behaviour (cmdc_gateway v0.4.1)

Copy Markdown View Source

Webhook dead-letter 抽象 behaviour — Phase 12M v0.4.1 (12M.1)。

Webhook 派发在 CMDCGateway.Webhook.dispatch/3 内做 3 次指数退避 重试后若仍失败,本来 v0.4.0 仅 log error 即丢弃。v0.4.1 引入 dead-letter 机制:派发最终失败 → 写入 dead-letter backend → 后台 CMDCGateway.Webhook.Dispatcher 定时 reload 待重试条目,按指数 退避策略再尝试派发。

Behaviour 4 callback

@callback init(opts :: keyword()) :: {:ok, state} | {:error, term()}
@callback save_failed(state, entry :: entry()) :: :ok | {:error, term()}
@callback list_pending(state) :: {:ok, [entry()]} | {:error, term()}
@callback delete(state, id :: String.t()) :: :ok | {:error, term()}
@callback mark_retried(state, id :: String.t(), updates :: map()) ::
            :ok | {:error, term()}

entry 结构

%{
  id: String.t(),                 # 入库时生成的唯一 ID
  callback_url: String.t(),
  body: binary(),                  # JSON-encoded payload
  secret: String.t() | nil,
  retry_count: non_neg_integer(), # 当前已重试次数(不含原始 3 次)
  max_retries: pos_integer(),     # 永久放弃前的最大次数
  next_retry_at_ms: integer(),    # System.system_time(:millisecond) 时间戳
  last_error: term() | nil,       # 上次失败原因(atom 或 inspect 字符串)
  first_failed_at_ms: integer(),  # 第一次失败时间
  last_attempted_at_ms: integer() # 上次重试时间
}

内置实现

Summary

Functions

构造一个标准 entry,方便 backend 实现 / Dispatcher 复用。

计算下次重试时间(指数退避)。

Types

entry()

@type entry() :: %{
  :id => id(),
  :callback_url => String.t(),
  :body => binary(),
  :secret => String.t() | nil,
  :retry_count => non_neg_integer(),
  :max_retries => pos_integer(),
  :next_retry_at_ms => integer(),
  :first_failed_at_ms => integer(),
  :last_attempted_at_ms => integer(),
  optional(:last_error) => term()
}

id()

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

state()

@type state() :: term()

Callbacks

delete(state, id)

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

init(opts)

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

list_pending(state)

@callback list_pending(state()) :: {:ok, [entry()]} | {:error, term()}

mark_retried(state, id, updates)

@callback mark_retried(state(), id(), updates :: map()) :: :ok | {:error, term()}

save_failed(state, entry)

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

Functions

build_entry(callback_url, body, secret, opts \\ [])

@spec build_entry(String.t(), binary(), String.t() | nil, keyword()) :: entry()

构造一个标准 entry,方便 backend 实现 / Dispatcher 复用。

next_retry(current_retry_count, opts \\ [])

@spec next_retry(
  non_neg_integer(),
  keyword()
) :: {non_neg_integer(), integer()}

计算下次重试时间(指数退避)。

返回 {retry_count + 1, next_retry_at_ms}

退避策略

  • 第 1 次重试:base_ms * 1(默认 30s)
  • 第 2 次:base_ms * 2(默认 60s)
  • 第 3 次:base_ms * 4(默认 120s)
  • 第 n 次:base_ms * 2^(n-1),最大 cap_ms(默认 3600s = 1 小时)