Zum Inhalt springen

Operations

Zwei Features für Background-Arbeit und gradueller Rollout neuer Features.

jobs

Status: ✅ Stable

Wofür: Background-Jobs (Email-Versand, periodische Aufräumarbeiten, Webhooks, Re-Indexing). Build auf BullMQ + Redis, mit Lane-Routing — du markierst pro Job, ob er in der api-Lane oder in einer separaten worker-Lane laufen soll, der Process-Topology folgt.

Wie es funktioniert: r.job({ id: "send-email", runIn: "worker", handler: async (ctx, payload) => { ... } }) registriert einen Job- Handler. Trigger-Wege:

  • Manuell: await ctx.jobs.enqueue("send-email", { ... })
  • Cron: r.job({ ..., schedule: "0 9 * * *" }) läuft täglich 9 Uhr
  • Event-getrieben: r.job({ ..., on: "incident:created" }) reagiert auf Domain-Events

runIn: "api" läuft im selben Bun-Prozess wie HTTP (gut für lokale Tests), runIn: "worker" braucht separate Worker-Container — Production-Topology mit horizontaler Skalierung.

Logs: Jeder Run wird in job_runs + job_run_logs festgehalten mit Status (succeeded/failed/retrying). Eingebaute UI-Page /admin/jobs zeigt die letzten Runs, Re-Run-Button für failed jobs.

Beispiel:

// Cron-Job: nightly cleanup
r.job({
id: "cleanup-old-attempts",
runIn: "worker",
schedule: "0 3 * * *", // täglich 3 Uhr morgens
handler: async (ctx) => {
await ctx.db.execute(sql`
DELETE FROM delivery_attempts
WHERE created_at < NOW() - INTERVAL '30 days'
`);
},
});
// Aus einem Write-Handler manuell triggern
await ctx.jobs.enqueue("send-incident-email", {
incidentId: id,
recipients: subscribers,
});

feature-toggles

Status: ✅ Stable

Wofür: Per-Tenant Feature-Flags. Operator kann eine neue Feature für einen Tenant freischalten ohne Deploy, oder ein experimentelles Verhalten gradual rausrollen.

Wie es funktioniert: Du deklarierst r.toggleable({ id: "new-billing", default: false }). Im Code: if (await ctx.toggles.isOn("new-billing")) { ... }. Operator schaltet über feature_toggle_set-Events oder einen Admin-Endpoint, der Cache invalidiert pro Tenant. State liegt in global_feature_state (für Plattform-Defaults) und in einer Per-Tenant-Override-Tabelle.

Best Practice: Toggle-Tod ist real — wenn ein Toggle 100% ausgerollt ist, lösch ihn aus dem Code. Toggles in Tests immer explizit setzen (siehe Coding-Standards: „Feature Flags in Tests explizit setzen/zurücksetzen”), nie auf Defaults verlassen.

Beispiel:

export const billingFeature = defineFeature("billing", (r) => {
r.toggleable({ id: "new-billing-pipeline", default: false });
r.queryHandler({
qn: "billing:invoice:list",
handler: async (ctx) => {
if (await ctx.toggles.isOn("new-billing-pipeline")) {
return ctx.db.list("invoice_v2");
}
return ctx.db.list("invoice_v1");
},
});
});

Siehe auch