116 lines
3 KiB
Markdown
116 lines
3 KiB
Markdown
# Workers
|
|
|
|
Use `Oban.Pro.Worker` with `args_schema` for type-safe, validated job arguments:
|
|
|
|
```elixir
|
|
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`:
|
|
|
|
```elixir
|
|
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:
|
|
|
|
```elixir
|
|
# 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:
|
|
|
|
```elixir
|
|
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
|
|
|
|
```elixir
|
|
@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:
|
|
|
|
```elixir
|
|
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:
|
|
|
|
```elixir
|
|
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]
|
|
```
|