towerops/vendor/oban_pro/usage-rules/workers.md

3 KiB

Workers

Use Oban.Pro.Worker with args_schema for type-safe, validated job arguments:

defmodule MyApp.SomeWorker do
  use Oban.Pro.Worker, queue: :default, max_attempts: 3

  args_schema do
    field :user_id, :integer, required: true
    field :status, :enum, values: ~w(pending active)a, default: :pending
    field :metadata, :map
  end

  @impl Oban.Pro.Worker
  def process(%Oban.Job{args: %__MODULE__{} = args}) do
    :ok
  end
end

Structured Args

Validates data when calling new/1, before insertion. Notable types:

  • :enum - Validates and casts to atoms: field :status, :enum, values: ~w(pending completed)a
  • :term - Safely encodes any Elixir term
  • :uuid - Alias for :binary_id
  • {:array, *} - Lists of typed values

Nested structures with embeds_one and embeds_many:

args_schema do
  field :order_id, :integer, required: true
  embeds_one :customer, required: true do
    field :customer_id, :integer, required: true
  end
  embeds_many :line_items do
    field :product_id, :integer, required: true
    field :quantity, :integer, required: true
  end
end

Uniqueness

Configure uniqueness with state groups, not explicit state lists:

# Good — uses the :incomplete group
use Oban.Pro.Worker, unique: [period: 60, states: :incomplete]

# Bad — explicit state lists drift out of sync with new states
use Oban.Pro.Worker, unique: [period: 60, states: [:available, :scheduled, :executing]]

Available groups: :incomplete (everything except :cancelled, :completed, :discarded) and :scheduled (just the scheduled state, safe for debouncing). Explicit lists silently miss states added in later releases and cause subtle duplication bugs.

Encrypted jobs

Protect sensitive args with transparent encryption:

use Oban.Pro.Worker,
  queue: :default,
  encrypted: [key: {Application, :fetch_env!, [:my_app, :oban_key]}]

Important: Only args are encrypted, never put sensitive data in meta.

Hooks

Execution hooks:

  • before_new/2 — modify args/opts before job creation
  • before_process/1 — runs before process/1
  • after_process/3 — runs after process/1 with state (:complete, :cancel, :discard, :error, :snooze), job, and result

External state hooks:

  • on_cancelled/2 — job cancelled externally (manual, deadline, dependency)
  • on_discarded/2 — job discarded by DynamicLifeline
@impl Oban.Pro.Worker
def after_process(state, job, result) do
  if state in [:error, :discard], do: notify_error(job)
  :ok
end

Attach hooks explicitly or globally:

use Oban.Pro.Worker, hooks: [MyApp.ErrorHook]

# Global hooks (in application startup)
Oban.Pro.Worker.attach_hook(:errors, MyApp.ErrorHook)

Deadlines

Cancel jobs that don't run within a time limit:

use Oban.Pro.Worker, deadline: {1, :hour}

# Override at runtime
MyWorker.new(%{}, deadline: {30, :minutes})

# Force cancel already-running jobs
use Oban.Pro.Worker, deadline: [in: {10, :minutes}, force: true]