|
| 1 | +// Copyright (c) 2026 Uber Technologies, Inc. |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +// Package hook defines the contract for a hook: a pluggable side effect run in |
| 16 | +// response to a pipeline lifecycle event. Warehouse exports, code-host comments, |
| 17 | +// notifications, and audit trails are all hooks. |
| 18 | +// |
| 19 | +// Which hooks run is a property of the event rather than of the deployment: two |
| 20 | +// queues in one host can point at different providers and want different |
| 21 | +// integrations. A host therefore supplies a Hooks resolver, and the controller |
| 22 | +// in platform/hook asks it once per event. |
| 23 | +// |
| 24 | +// Hooks run behind a durable queue, never inline in the pipeline, so a slow or |
| 25 | +// failing integration cannot stall or fail the work that triggered it. |
| 26 | +package hook |
| 27 | + |
| 28 | +//go:generate mockgen -source=hook.go -destination=mock/hook_mock.go -package=mock |
| 29 | + |
| 30 | +import ( |
| 31 | + "context" |
| 32 | + |
| 33 | + basehook "github.com/uber/submitqueue/api/base/hook" |
| 34 | +) |
| 35 | + |
| 36 | +// Hook performs a side effect in response to a lifecycle event. |
| 37 | +type Hook interface { |
| 38 | + // Handle performs the side effect for event. |
| 39 | + // |
| 40 | + // Delivery is at-least-once, so the same event — identical id — may arrive |
| 41 | + // more than once, including after a successful Handle. Implementations must |
| 42 | + // be idempotent on the event id. |
| 43 | + // |
| 44 | + // Returning nil means "done with this event", which is also how a hook |
| 45 | + // ignores one: there is no filter or subscription API, because a hook that |
| 46 | + // does not care about a type simply returns nil, and routing can be added as |
| 47 | + // a wiring decorator if it ever pays for itself. |
| 48 | + // |
| 49 | + // Returning an error retries the event and, past the retry budget, |
| 50 | + // dead-letters it. Return plain errors; classification is the consumer's |
| 51 | + // job. An error must mean the side effect did not happen — reporting failure |
| 52 | + // for work that succeeded turns at-least-once into repeated duplicate |
| 53 | + // effects. |
| 54 | + // |
| 55 | + // A hook must never write pipeline state. Its outcome is invisible to the |
| 56 | + // pipeline by design: that is what makes the side effect unable to affect |
| 57 | + // the transition that triggered it. |
| 58 | + Handle(ctx context.Context, event *basehook.HookEvent) error |
| 59 | + |
| 60 | + // Name identifies the hook in logs, metrics, and the failure attribution |
| 61 | + // the controller reports. Stable and unique among the hooks a host wires. |
| 62 | + Name() string |
| 63 | +} |
| 64 | + |
| 65 | +// Hooks resolves the hooks that run for an event. |
| 66 | +type Hooks interface { |
| 67 | + // For returns the hooks to run for event, in the order they should run. |
| 68 | + // Returning none is an ordinary outcome: it means nothing this host wired |
| 69 | + // is interested in the event. |
| 70 | + // |
| 71 | + // It takes the event rather than a queue name because the envelope carries |
| 72 | + // no queue. Which scope selects hooks differs per domain — queue, source, |
| 73 | + // event type — and only the host that publishes the payload can read a |
| 74 | + // queue out of it, so the choice belongs to the resolver. |
| 75 | + // |
| 76 | + // Called on every delivery, so resolution must be cheap and must not fail: |
| 77 | + // an integration that cannot be reached is a Handle error, not an absent |
| 78 | + // hook. |
| 79 | + For(event *basehook.HookEvent) []Hook |
| 80 | +} |
0 commit comments