tako docs guide reference api
GitHub

tako.Handler — what a background declaration returns

Five constructors hand one back: Worker, Subscribe, Runner, Cron and Singleton. A Handler is that declaration's binding — the value you name when you want to say that handler somewhere else.

This page bends the rubric like cli.md does: a Handler is not a feature you declare, it is the value five features return. The HTTP half of the story is Endpoint, which is what tako.API and tako.Page return instead.

Declare

You never write the type — you receive it, always into a package-level var. From examples/gallery/app.go and examples/shop/app.go:

var (
    WarmFeed = tako.Subscribe(ready, warmFeed)
    Cleanup  = tako.Cron("every 24h", cleanupOrphans, tako.CronConfig{Singleflight: true})
    Settle   = tako.Runner(settle, runSettle, tako.RunnerConfig{Parallel: 4})
    Invoicer = tako.Singleton(assignInvoices)
)

Constructors are side-effect-free records, so declaration order never matters. The var's name is the identity — not the function's. That is the name tako deploys the handler under, the name in tako gen graph, and the name in a log line; makeThumb the function is a label, and renaming it changes nothing outside your own file.

Use — a Handler is a name, not a callable

It has no exported methods, deliberately. What you do with one is name it:

// alerts, which is where most Handlers get named
Silent  = tako.Alert(tako.CronMissed(Cleanup, tako.Window{For: 8 * time.Minute}), oncall)
Failing = tako.Alert(tako.CronFailed(Cleanup, 1, tako.Window{Every: 5 * time.Minute}), oncall)
Stopped = tako.Alert(tako.SingletonDown(Invoicer, tako.Window{For: 2 * time.Minute}), oncall)
Hungry  = tako.Alert(tako.WhenAbove(
    tako.ServiceMetric(MakeThumb,
        "run.googleapis.com/container/memory/utilizations"), 0.9,
    tako.Window{For: 10 * time.Minute}), oncall)

A Handler satisfies Target — anything an alert can be about — and a Subscribe handler also satisfies Consumer, because a subscriber falls behind and dead-letters independently of every other subscriber on the same event.

There is no Call. Invoking a worker directly would be a second way to run the work — one that skips the queue that gives it its retries, its dead letter and its at-least-once delivery. You publish to the queue; you don't call the worker. The same holds for a cron: you wait, or in a test you advance the clock.

Each Handler carries its kind, and the analyzer checks the kind against what you asked for — naming a worker where a cron belongs is a refusal, not a surprise at 3am.

Local

tako dev and tako test collapse every handler into the one process that serves the app — workers, subscribers, crons, jobs and singletons all register in-process. Queues are channels, and crons really tick: on a real clock under tako dev, on the test clock under AdvanceCron.

Declaring a Handler is still a side-effect-free record, so declaration order never matters and the generated main is what wires it — which is why a handler declared inside a function is invisible to analysis (see the errors below). The one carve-out: async constructors also append their record to an in-process registry read only by NewTestApp, so a test needs no list of handlers to drain.

Cloud

Each Handler becomes its own deployable, named <package>-<var> lowercased: MakeThumb in package gallery deploys as gallery-makethumb, Settle in shop as shop-settle. Which kind depends on the constructor — Worker, Subscribe, Cron and Singleton become Cloud Run services; Runner becomes a Cloud Run Job (job.md). Services are private, reached over authenticated push from Pub/Sub or Cloud Scheduler and never from the internet, and each deployable gets its own service account carrying only the permissions its own code edges imply.

Two consequences worth knowing before you rename a var:

Test it

AdvanceCron takes the Handler itself, which is the whole reason the var exists. From examples/gallery/app_test.go:

app := tako.NewTestApp(t)
app.AdvanceCron(Cleanup)   // fire the schedule once, now
app.DrainQueues()          // let whatever it published run

Workers and subscribers need no naming at all — DrainQueues runs whatever is pending — so Runner, Worker and Subscribe handlers are usually declared into vars for the alerts and the deployed name rather than for the tests. See testing.md.

Errors you'll meet

A handler that is not a package-level var cannot be analyzed, so it cannot be deployed:

app.go:6:6: tako.API must initialize a package-level var — handlers declared inside functions are invisible to analysis (tako/package-level)

Naming the wrong kind of handler in an alert:

app.go:28:41: tako.CronMissed watches a cron, but alerts.Work is a worker (tako/alert-subject)

Two queues cannot share one:

app.go:11:26: queue "jobs" already has worker First (app.go:9) — a queue has exactly one worker; use a Topic for fan-out (tako/one-worker)

Not yet