sub
The sub package defines the internal Subscription struct that facilitates the endpoint subscriptions of the microservice. It transforms the partial path specification in Connector.Subscribe to produce a fully-qualified URL.
| Path specification | Fully-qualified URL |
|---|---|
| (empty) | https://example.host |
| / | https://example.host/ |
| :1080 | https://example.host:1080 |
| :1080/ | https://example.host:1080/ |
| :1080/path | https://example.host:1080/path |
| /path/with/slash | https://example.host:443/path/with/slash |
| path/with/no/slash | https://example.host:443/path/with/no/slash |
| /path/{argument}/or/{suffix…} | https://example.host:443/path/{argument}/or/{suffix...} |
| https://another.host/path | https://another.host:443/path |
| https://another.host:1080/path | https://another.host:1080/path |
This package also defines various Options that can be applied to the Subscription using the options pattern. This pattern is used in Go for expressing optional arguments.
For example:
con.Subscribe("MyHandler", handler,
sub.At("GET", "/path"),
sub.Web(),
sub.NoQueue(),
)Connector.Subscribe takes a unique name (a Go identifier) and a handler as required positional arguments, then a variadic list of options. Exactly one feature option must be applied - sub.Function, sub.Web, sub.InboundEvent, sub.Task, or sub.Workflow - to declare the kind of endpoint being registered. Connector.Unsubscribe(name) removes a registration by name.
The HTTP method passed to sub.At (or its method-specific variants) is validated at registration time. Only the standard HTTP verbs - GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH - and the wildcard ANY are accepted. Matching is case-insensitive. Unknown methods are rejected when the subscription is registered, so a typo in a method string fails fast at startup rather than producing silently unreachable endpoints.
Manual Activation
By default, every subscription comes onto the bus during Startup and stays on until OnShutdown. The sub.Manual() option opts a subscription out of the automatic activate/deactivate passes - it stays registered with the connector but does not join the transport until user code explicitly activates it.
con.Subscribe("Embed", svc.doEmbed,
sub.At("POST", "/embed"),
sub.Function(embedderapi.EmbedIn{}, embedderapi.EmbedOut{}),
sub.Manual(), sub.Tag("python"),
)User code drives the lifecycle via two connector methods:
Connector.ActivateSubscription(name) error- brings the named subscription onto the bus. Idempotent (already-active is a no-op). Valid while the connector is starting up, started, or shutting down, so a manual sub can come on-bus alongside the lifecycle of its backing resource - for example, a Python venv that takes 30 seconds to allocate insideOnStartup, or a distributed cache being torn down insideOnShutdown.Connector.DeactivateSubscription(name) error- takes the named subscription off the bus while leaving it registered. Unicast callers see a clean404ack-timeout (load-balancing routes around the cold replica); the connector preserves the registration so a subsequentActivateSubscriptionbrings it back.
The intent is to let a manual subscription be activated only when the resource it depends on is ready, and deactivated when that resource has been reclaimed or has failed. This is what makes Python integration clean: the upstream microservice goes live on the bus for control-plane traffic during OnStartup, but its Python-backed endpoints stay off until the venv is provisioned, so callers never see confusing 503s while pip-install runs in the background.
sub.Automatic() clears the manual flag and restores the default automatic behavior. It exists for symmetry in programmatically composed option lists.
Tags
The sub.Tag(tags ...string) option attaches one or more free-form labels to a subscription. Tags surface through Connector.Subscriptions() and let user code group related subscriptions for bulk operations:
for _, s := range con.Subscriptions() {
if !slices.Contains(s.Tags, "python") {
continue
}
if err := con.ActivateSubscription(s.Name); err != nil {
return errors.Trace(err)
}
}Tag names are not parsed by the framework; convention is short, lowercase, single-word identifiers (python, billing, experimental). Multiple sub.Tag(...) calls accumulate; sub.Untag(...) removes tags previously attached.
Connector.Subscriptions() returns a read-only snapshot of every registered subscription, including its name, route, type, tags, manual flag, and active state. The combination lets user code express “all Python tasks” or “all manual subs that need to come up after my downstream cache is warm” without a custom query API on the connector.
Time Budget
The sub.TimeBudget(d time.Duration) option declares the maximum duration this endpoint’s handler may run. The inbound request’s context deadline is shortened to the smaller of the caller-provided budget and this declared budget, so the handler observes ctx.Done() and should return promptly when it fires:
con.Subscribe("Summarize", svc.summarize,
sub.At("POST", "/summarize"),
sub.Function(api.SummarizeIn{}, api.SummarizeOut{}),
sub.TimeBudget(5*time.Second),
)This is endpoint self-protection: it caps how long this handler can tie up a worker, independent of how patient the caller is. The declared budget is not carried on the wire — an upstream caller does not learn the callee’s budget and keeps waiting its own configured timeout; propagation happens naturally through the handler returning fast once its context is cancelled. A zero or negative duration declares no budget.
For workflow task endpoints this composes with the foreman’s per-step ceiling: the effective deadline is the smaller of the foreman’s TimeBudget config (or the graph’s per-task budget) and the endpoint’s own sub.TimeBudget.