Dispatch

Architecture

How Dispatch's packages fit together.

Dispatch is organized as a set of focused Go packages. The root dispatch package defines the core Dispatcher and configuration. The engine package wires everything together. Application code interacts primarily with engine.

Package diagram

┌──────────────────────────────────────────────────────────┐
│                     engine.Engine                         │
│  Register / Enqueue / RegisterWorkflow / RegisterCron     │
│  Build(dispatcher, ...options)                            │
├───────────┬───────────┬──────────────┬────────────────────┤
│  job      │ workflow  │   cron       │   cluster          │
│ Registry  │ Registry  │  Scheduler   │ (leader election,  │
│ Executor  │  Runner   │ (leader only)│  heartbeats,       │
│           │           │              │  work stealing)    │
├───────────┴───────────┴──────────────┴────────────────────┤
│                    middleware chain                        │
│   Logging → Recover → Timeout → Tracing → Metrics → Scope │
├───────────────────────────────────────────────────────────┤
│                    ext.Registry                            │
│   OnJobCompleted, OnWorkflowFailed, OnCronFired, ...      │
├──────────────────────────────────────────────────────────┤
│                      store.Store                          │
│   (job + workflow + cron + dlq + event + cluster stores) │
├──────────┬──────────┬──────────┬──────────────────────────┤
│ Postgres │   Bun    │  SQLite  │  Memory (testing only)   │
│ (pgx/v5) │  (ORM)   │          │                          │
└──────────┴──────────┴──────────┴──────────────────────────┘

The import-cycle problem and the engine layer

The root dispatch package defines Entity, Config, and error sentinels. It is imported by every subsystem package (job, workflow, cron, etc.). This means the root package cannot import any subsystem package without creating a cycle.

The engine package solves this: it sits above all subsystem packages and wires them together. Application code calls engine.Build, engine.Register, and engine.Enqueue rather than constructing subsystems manually.

Job execution critical path

When you call engine.Enqueue(ctx, eng, SendEmail, input):

  1. Serialize — The typed input is JSON-marshaled to []byte.
  2. Persist — A job.Job record is created in pending state with RunAt = now.
  3. Poll — On the next PollInterval tick (default: 1s), the worker pool dequeues the job.
  4. Middleware — The job passes through the configured middleware chain.
  5. Execute — The HandlerFunc for the job name is called with the deserialized payload.
  6. Outcome — The job is marked completed, or transitions to retrying / failed / dlq.

Store composition

The store.Store interface composes six subsystem store interfaces:

type Store interface {
    job.Store
    workflow.Store
    cron.Store
    dlq.Store
    event.Store
    cluster.Store

    Migrate(ctx context.Context) error
    Ping(ctx context.Context) error
    Close() error
}

Each subsystem defines its own store interface independently. A single backend implementation satisfies all of them. The memory, PostgreSQL, Bun, SQLite, and Redis backends all implement this interface.

Extension and middleware composition

Extensions and middleware are independent composition axes:

  • Middleware executes synchronously in-band with each job. It can short-circuit, add context, or measure latency.
  • Extensions are called out-of-band after state transitions (job completed, workflow failed, etc.). They are fire-and-forget.

Package index

PackageDescription
dispatchRoot — Dispatcher, Config, Storer, options, errors, Entity
engineWires subsystems; Build, Register, Enqueue, RegisterWorkflow, RegisterCron
jobJob entity, State machine, Definition[T], Registry
workflowDefinition[T], Run, RunState, step checkpointing
cronEntry, Scheduler, distributed leader-elected cron
dlqEntry, Service — list, replay, purge
eventEvent entity and store interface
clusterWorker, leader election, heartbeats, work stealing
queueConfig, Manager — per-queue rate limiting and concurrency
middlewareMiddleware, Chain, built-ins (Logging, Recover, Timeout, Tracing, Metrics, Scope)
extExtension interface, lifecycle hook interfaces, Registry
backoffRetry backoff strategies
observabilityOpenTelemetry MetricsExtension for system-wide counters
idTypeID-based identifiers (JobID, RunID, CronID, DLQID, WorkerID, …)
apiForge-style HTTP admin API handlers
scopeForge scope helpers — tenant ID extraction from context
relay_hookRelay webhook delivery extension
extensionForge framework integration adapter
storeComposite Store interface
store/memoryIn-memory backend (development and testing)
store/postgresPostgreSQL backend (pgx/v5)
store/bunBun ORM backend
store/sqliteSQLite backend
store/redisRedis backend
cluster/k8sKubernetes consensus for leader election

On this page