Idempotency
Ensure API calls with side effects are idempotent to prevent duplicates.
Idempotency
Ensure API calls with side effects are idempotent to prevent duplicates.
Granular Steps
Break workflows into small, self-contained units of work.
State Management
Store state through step outputs, not in-memory variables.
Event Immutability
Never mutate incoming events, create new state instead.
Before performing non-idempotent operations (e.g., payments), verify the operation hasn’t already been completed. This is crucial for handling potential retries correctly. See Retry Policies.
export default defineWorkflow('charge-customer', async (flow) => { await flow.do('charge customer', async () => { // First, check if already charged const subscription = await fetch( `https://payment.api/subscriptions/${customerId}` ).then((res) => res.json());
if (subscription.charged) { return; // Skip if already charged }
// Proceed with charging return await fetch( `https://payment.api/subscriptions/${customerId}`, { method: 'POST', body: JSON.stringify({ amount: 10.0 }), } ); });});Each step should perform a single, cohesive unit of work that can be retried independently. See Developing Workflows for more on structuring steps.
export default defineWorkflow('fetch-and-display-cat', async (flow) => { // Separate steps for different operations const catId = await flow.do('fetch cat id from KV', async ({ use }) => { return await use(KV).get('cutest-cat-id'); });
const catImage = await flow.do('fetch cat image', async () => {return await fetch(`https://api.cat-images.com/${catId}`);});
return catImage;});Store state exclusively through step outputs rather than local variables that exist only in memory. The engine persists the output of completed steps (flow.do).
export default defineWorkflow('build-cat-gallery', async (flow) => { // Good: Build state from step outputs const catList = await Promise.all([ flow.do('fetch cat 1', async ({ use }) => await use(KV).get('cat-1') ), flow.do('fetch cat 2', async ({ use }) => await use(KV).get('cat-2') ), ]);
await flow.sleep('wait before display', '3 hours');
// Use persisted state return await flow.do('display random cat', async () => { const randomCat = catList[Math.floor(Math.random() * catList.length)]; return await fetch(`https://api.cat-images.com/${randomCat}`); });});Never mutate incoming events. Create and return new state when changes are needed. This aligns with Event-Sourced Architecture principles.
export default defineWorkflow('process-user-event', async (flow) => { // Bad: Mutating event payload await flow.do('bad mutation', async () => { event.payload.user = await use(KV).get(event.payload.user); });
// Good: Return new stateconst userData = await flow.do('fetch user data', async () => {return await use(KV).get(event.payload.user);});// Use userData in subsequent steps});Give each step a stable, unique identifier rather than one based on timestamps or randomness. This supports Deterministic Execution.
export default defineWorkflow('process-orders', async (flow) => { // Bad: Non-deterministic name await flow.do(`process order at ${Date.now()}`, async () => { // ... });
// Good: Static name await flow.do('process order', async () => { // ... });});Granularity
Divide workflows into small, self-contained steps.
Idempotency
Check before performing non-idempotent operations.
State Management
Persist state only via step outputs.
Immutability
Treat events as immutable, return new state for changes.