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):
- Serialize — The typed input is JSON-marshaled to
[]byte. - Persist — A
job.Jobrecord is created inpendingstate withRunAt = now. - Poll — On the next
PollIntervaltick (default: 1s), the worker pool dequeues the job. - Middleware — The job passes through the configured middleware chain.
- Execute — The
HandlerFuncfor the job name is called with the deserialized payload. - Outcome — The job is marked
completed, or transitions toretrying/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
| Package | Description |
|---|---|
dispatch | Root — Dispatcher, Config, Storer, options, errors, Entity |
engine | Wires subsystems; Build, Register, Enqueue, RegisterWorkflow, RegisterCron |
job | Job entity, State machine, Definition[T], Registry |
workflow | Definition[T], Run, RunState, step checkpointing |
cron | Entry, Scheduler, distributed leader-elected cron |
dlq | Entry, Service — list, replay, purge |
event | Event entity and store interface |
cluster | Worker, leader election, heartbeats, work stealing |
queue | Config, Manager — per-queue rate limiting and concurrency |
middleware | Middleware, Chain, built-ins (Logging, Recover, Timeout, Tracing, Metrics, Scope) |
ext | Extension interface, lifecycle hook interfaces, Registry |
backoff | Retry backoff strategies |
observability | OpenTelemetry MetricsExtension for system-wide counters |
id | TypeID-based identifiers (JobID, RunID, CronID, DLQID, WorkerID, …) |
api | Forge-style HTTP admin API handlers |
scope | Forge scope helpers — tenant ID extraction from context |
relay_hook | Relay webhook delivery extension |
extension | Forge framework integration adapter |
store | Composite Store interface |
store/memory | In-memory backend (development and testing) |
store/postgres | PostgreSQL backend (pgx/v5) |
store/bun | Bun ORM backend |
store/sqlite | SQLite backend |
store/redis | Redis backend |
cluster/k8s | Kubernetes consensus for leader election |