Zizq.JobKind behaviour (Zizq v0.6.0)

Copy Markdown View Source

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
end

use 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

Callbacks

Run the job, given its payload.

Run the job, given its payload and the Zizq.Job it came from.

Types

result()

@type result() ::
  :ok
  | {:ok, term()}
  | {:error, term()}
  | {:cancel, term()}
  | {:snooze, non_neg_integer() | DateTime.t()}

Callbacks

perform(payload)

(optional)
@callback perform(payload :: term()) :: result()

Run the job, given its payload.

perform(payload, job)

(optional)
@callback perform(payload :: term(), job :: Zizq.Job.t()) :: result()

Run the job, given its payload and the Zizq.Job it came from.