Defines a kind of job: its name on the server, the enqueue options it defaults to, and what running it does.
defmodule MyApp.SendEmail do
use Zizq.JobKind,
type: "send_email",
queue: "emails",
retry_limit: 5
@impl Zizq.JobKind
def perform(%{"user_id" => id, "template" => template}) do
MyApp.Mailer.deliver(id, template)
end
enduse generates type/0, new/1 and new/2 on the module. Building
an enqueue and sending it stay separate, so enqueues compose:
MyApp.SendEmail.new(%{"user_id" => 42})
|> Zizq.enqueue(MyApp.Zizq)
users
|> Enum.map(&MyApp.SendEmail.new(%{"user_id" => &1.id}))
|> Zizq.enqueue_all(MyApp.Zizq)new/2 takes per-enqueue overrides on top of the module's defaults:
MyApp.SendEmail.new(%{"user_id" => 42}, priority: 10)Options
:type is the job's name on the server — the "type" field a
producer written in any language sends to reach this handler. It is
required and never inferred from the module name, so renaming
MyApp.SendEmail cannot silently change the wire contract and strand
every queued job.
Everything else is optional and is any key Zizq.Enqueue accepts
except :payload, which belongs to the individual enqueue rather
than the kind. Options are validated when the module compiles, so a
malformed :backoff is a build failure rather than a surprise at the
first enqueue.
:queue defaults to "default" in the client. Anything left unset
is omitted from the request entirely, so the server's own defaults
apply and keep tracking its configuration.
perform/1 and perform/2
Define whichever you need. perform/2 also receives the
Zizq.Job, for the attempt count, id, queue, etc:
@impl Zizq.JobKind
def perform(payload, %Zizq.Job{attempts: attempts}) when attempts >= 3 do
MyApp.Mailer.deliver_without_retry(payload)
end
def perform(payload, _job), do: MyApp.Mailer.deliver(payload):attempts counts attempts that have already finished, so it is 0
while a job runs for the first time and the guard above first matches
on the fourth run. Note that Zizq.ErrorRecord's :attempt numbers
the attempt a failure belongs to, so it reads one higher than the
:attempts a handler saw during that same run.
Defining both is fine — perform/2 wins. Defining neither fails at
compile time. The choice is resolved while the module compiles, so
dispatch costs nothing at runtime.
See Zizq.Worker for what a return value does to the job.
Producing without consuming
A module is only worth defining where the job is run. An
application that merely enqueues work handled elsewhere — by another
service, or another language — has no perform to write, and should
build enqueues with Zizq.Enqueue directly.
Summary
Types
@type result() :: :ok | {:ok, term()} | {:error, term()} | {:cancel, term()} | {:snooze, non_neg_integer() | DateTime.t()}