go get github.com/alob-mtc/runnerq-goRequires Go 1.27+ and a PostgreSQL database (12+).
The Postgres backend creates its own schema on first connect (idempotently), so there's no migration step:
import "github.com/alob-mtc/runnerq-go/storage/postgres"
backend, err := postgres.New(ctx, "postgres://user:pass@localhost:5432/mydb", "my_app")
if err != nil {
log.Fatal(err)
}
defer backend.Close()The second argument is the queue name — a logical namespace stored as a
column, so many independent queues can share one database. For lease and pool
tuning, use postgres.WithConfig (see Configuration).
A workflow is an activity handler — a type implementing ActivityHandler.
Embed DefaultDeadLetterHandler unless you need a dead-letter callback. The
handler's type name doubles as its activity type (Greeting here). You can
also pick the name yourself; both forms are shown at the end of this page.
type Greeting struct {
runnerq.DefaultDeadLetterHandler
}
func (h *Greeting) Handle(ctx runnerq.ActivityContext, payload json.RawMessage) (json.RawMessage, error) {
name := string(payload)
return json.Marshal("hello, " + name)
}engine, err := runnerq.Builder().
Backend(backend).
MaxWorkers(8).
Build()
if err != nil {
log.Fatal(err)
}
engine.RegisterActivity(&Greeting{}) // serves activity type "Greeting"
// Start blocks until the context is cancelled or a SIGINT/SIGTERM arrives.
go engine.Start(ctx)future, err := engine.GetActivityExecutor().
Activity[Greeting]().
Payload(json.RawMessage(`"world"`)).
Execute(ctx)
if err != nil {
log.Fatal(err)
}
result, err := future.GetResult(ctx) // blocks until the activity completes
fmt.Println(string(result)) // "hello, world"Everything above uses the typed API: the handler type is the activity name, so there is no string to keep in sync. The named API does the same job with a string you choose. Use it when the name must survive a refactor, when one handler serves several types, or when the producer has no Go type in scope. The two are interchangeable, line for line:
// Typed (recommended)
engine.RegisterActivity(&Greeting{})
engine.GetActivityExecutor().Activity[Greeting]().Payload(p).Execute(ctx)
// Named
engine.RegisterActivityWithName("greeting", &Greeting{})
engine.GetActivityExecutor().ActivityNamed("greeting").Payload(p).Execute(ctx)The named form is also the shape of the v0.5 API. RunnerQ v0.6+ requires Go 1.27 for the typed methods; if you cannot move to 1.27 yet, stay on v0.5, where the named form is the only one and your code carries over unchanged apart from the method names. See Naming activities for the full picture.
Stop the engine and wait for the graceful drain before closing the backend:
done := make(chan struct{})
go func() { defer close(done); engine.Start(ctx) }()
// ... later ...
engine.Stop() // stop intake, let in-flight handlers finish and ack
<-done
backend.Close()- The examples — runnable, realistic, copy-pasteable.
- Durable Execution — make workflows survive crashes.