Oban for Go

github.com/malcolmston/oban

Background job processing for Go, standard library only.

 GitHubports sorentwo/oban

An Oban/Sidekiq-style background job system for Go built on nothing but the standard library. An Oban engine runs named queues at configured concurrency, resolves workers by name from a Registry, and executes each job with a per-attempt timeout. Failures retry with exponential backoff and jitter until they succeed or exhaust their attempts, at which point they are discarded. The engine also de-duplicates unique jobs, schedules periodic work with cron expressions, wraps every attempt in middleware/telemetry, and shuts down gracefully by draining in-flight jobs. It is deterministic and testable: time flows through an injectable clock, backoff jitter is seedable, and cron scheduling is pure. A complete in-memory Store ships in the box, and the Store interface documents exactly what a database-backed implementation must guarantee.

Install

shell
$ go get github.com/malcolmston/oban

Quick start

main.go
import "github.com/malcolmston/oban"

engine, _ := oban.New(oban.Config{
    Store:  oban.NewInMemoryStore(),
    Queues: map[string]int{"default": 5, "mailers": 2},
})

// Register a worker by name.
engine.RegisterFunc("email", func(ctx context.Context, job *oban.Job) error {
    var args struct{ To string `json:"to"` }
    if err := job.UnmarshalArgs(&args); err != nil {
        return err
    }
    return sendEmail(ctx, args.To)
})

_ = engine.Start(ctx)
job, _ := oban.NewJob("email", map[string]string{"to": "ada@example.com"},
    oban.WithQueue("mailers"), oban.WithMaxAttempts(5))
_, _, _ = engine.Enqueue(ctx, job)

Features

  • Oban engine — polls named queues at configured concurrency, built with New(Config{...}) and driven by Start / Stop
  • Jobs as data — a Job carries queue, JSON Args, attempts, priority, schedule and error history; build one with NewJob
  • Named workers — implement Worker (Perform(ctx, *Job) error) or register a func with RegisterFunc in a Registry
  • Retries with backoff — the Backoff interface, with ExponentialBackoff growing the delay and adding seedable jitter up to a cap
  • Discard on exhaustion — jobs that use up MaxAttempts transition to discarded and fire the ErrorHandler
  • Cron scheduling — declare Periodic jobs against a parsed 5-field Schedule; Schedule.Next is a pure function
  • Unique jobs — WithUnique(key, period) de-duplicates by queue + worker + key over a time window
  • Middleware & telemetry — wrap every attempt with a Middleware chain, with Telemetry installed innermost to time the worker
  • Pluggable persistence — a complete InMemoryStore ships in the box; implement Store (SELECT ... FOR UPDATE SKIP LOCKED semantics) for a database
  • Deterministic & testable — the engine owns time through an injectable Clock, so scheduling, backoff and uniqueness test without real sleeps
  • Graceful shutdown — Stop stops fetching and drains in-flight work, honouring the context deadline
  • Zero dependencies — pure Go standard library, no cgo, nothing to audit but the toolchain
Oban