🔗 Chain Worker
Chain workers link jobs together to ensure they run in a strict, sequential, FIFO order. Downstream
jobs automatically suspend execution until the upstream job is completed
. Jobs in a chain only
run after the previous job completes successfully, regardless of snoozing or retries.
Through a declarative syntax, chains can partition by worker
, args
, or meta
, and each
partition runs sequentially while the queue runs with any level of concurrency:
defmodule MyApp.WebhookWorker do
use Oban.Pro.Workers.Chain, on_cancelled: :halt, by: [:worker, args: :account_id]
...
Previously, you could approximate chain behaviour with a global limit of 1, but it lacked guarantees around retries, cancellation, or even scheduled jobs.
See the Chain docs for details and examples of how to handle customize error handling.
📐 Structured Additions
Structured jobs, as declared with args_schema
, gain a few new capabilities in this release.
-
:default
— Any field can have a default value, calculated at compilation time and applied at runtime. -
{:array, :enum}
— Now it's possible to cast and validate the values of a list with an array of enums, e.g.{:array, :enum}, values: ~w(foo bar baz)a
-
:term
— Safely encodes any Elixir term as a string for storage, then decodes it back to the original term on load. This is similar to:any
, but works with terms like tuples or pids that can't usually be serialized. For safety, terms are encoded with the:safe
option to prevent decoding data that may be used to attack the runtime.
Here's a toy example that demonstrates all three additions:
args_schema do
field :pid, :term, required: true
field :flags, {:array, :enum}, values: ~(fast heavy lazy)a
field :debug, :boolean, default: false
end
def process(job) do
send(job.args.pid, :pids_work!)
end
🧰 New assert_enqueue/2 and refute_enqueue/2 helpers
Assert or refute that jobs were enqueued during a function call. These new test helpers receive a function and detect only jobs that were (or weren't) enqueued while running the function.
More specifically, the helpers:
- Ignore any jobs enqueued before the function ran
- Return whatever the function returned when the assertion passes
- Respect the same filtering syntax as other assertion helpers
For example, this combines assert_enqueue/2
with perform_job/2
to assert that a job is
enqueued by a worker's perform
:
assert_enqueue [worker: MyApp.OtherWorker, queue: :business], fn ->
perform_job(MyApp.SomeWorker, %{id: account.id})
end