Go SDK
Go 1.22+ SDK. Lives at workers/go/. Standard library only — no third-party dependencies. Go has no annotations, so registration is plain function calls.
Install
go get github.com/kinarix/conduit/workers/go@latest
Quick start with Register
package main
import (
"context"
"encoding/json"
"log"
"net/http"
"os"
"os/signal"
"syscall"
cw "github.com/kinarix/conduit/workers/go/conduitworker"
)
func main() {
client := cw.NewClient(cw.ClientConfig{BaseURL: "http://localhost:8080"})
runner := cw.NewRunner(client, "http-worker-1")
runner.Register("http.call", func(ctx context.Context, task *cw.ExternalTask) (*cw.Result, error) {
var url string
if raw, ok := task.Variable("url"); ok {
_ = json.Unmarshal(raw, &url)
}
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err // → /failure (engine decrements retries)
}
defer resp.Body.Close()
if resp.StatusCode == 402 {
return cw.BpmnError("PAYMENT_DECLINED", "card declined"), nil
}
return cw.Complete(cw.VarLong("status", int64(resp.StatusCode))), nil
})
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
if err := runner.Run(ctx); err != nil && err != context.Canceled {
log.Fatal(err)
}
}
HandlerFunc is func(ctx context.Context, task *ExternalTask) (*Result, error). Register as many as you like before calling Run.
RunnerConfig
NewRunner(client, workerID) ships sensible defaults: MaxJobs=10, LockDurationSecs=30, PollInterval=1s. Override with SetConfig:
runner.SetConfig(cw.RunnerConfig{
WorkerID: "http-worker-1",
MaxJobs: 25, // 1..100
LockDurationSecs: 60,
PollInterval: 500 * time.Millisecond, // idle back-off
})
runner.Run(ctx) returns ctx.Err() when the context is cancelled — wire it to signal.NotifyContext for graceful shutdown.
Reporting outcomes
| Return | Engine call |
|---|---|
cw.Complete(vars...), nil | /complete |
cw.BpmnError(code, message, vars...), nil | /bpmn-error |
nil, err | /failure |
cw.Complete and cw.BpmnError are variadic over cw.Variable:
cw.Complete(
cw.VarLong("status", 201),
cw.VarString("order_id", "ord-42"),
)
cw.BpmnError("DENIED", "policy violation")
Variables
cw.VarString("name", "alice")
cw.VarLong("count", 42)
cw.VarDouble("ratio", 0.75)
cw.VarBool("approved", true)
cw.VarJSON("payload", map[string]any{"x": 1})
cw.VarNull("tombstone")
Read off the task — Variable(name) returns the raw JSON bytes plus an “exists” flag, so you decode into the type you expect:
var orderID string
if raw, ok := task.Variable("order_id"); ok {
_ = json.Unmarshal(raw, &orderID)
}
// or for any structured value:
var payload map[string]any
if raw, ok := task.Variable("payload"); ok {
_ = json.Unmarshal(raw, &payload)
}
Building and running
cd workers/go
go vet ./...
go test ./... # 3 round-trip tests against net/http/httptest
go build ./cmd/http-worker # reference binary scaffold
./http-worker
Concurrency
The runner’s dispatch loop is sequential: tasks within a single fetch round run one at a time. For more parallelism, fan out from inside your handler (go func() { ... }()) onto your own goroutine pool, or run multiple processes with distinct worker_ids.
Idempotency
Same contract as every other SDK — see Idempotency.