When building workflows you may need to perform operations that have side effects, like:

  • Sending an email
  • Logging
  • Making a database call
  • API requests

Having workflow as code means you are free to place these side effects anywhere in your workflow definition.

But, there arises a problem since the workflow definition may be executed multiple times.

  1. Side-effects could be executed multiple times.
  2. The result of the side-effect may not be consistent across executions.
workflow.version(1).define(async (ctx, input) => {
  // ... your workflow code

  // ⚠️ This may run multiple times!
  const user = await fetch(
    "http://user-service.local.svc.cluster.local/users/1",
  );

  // Rest of the workflow...
});

Memoized Results

To solve this problem, Inferable provides a memo convenience function that can be used to cache interim results.

workflow.version(1).define(async (ctx, input) => {
  // ... existing workflow code

  // ✅ This will cache the result of the fetch and return the cached result for subsequent executions
  const user = await ctx.memo("getUser", async () => {
    return fetch("http://user-service.local.svc.cluster.local/users/1");
  });

  // ... existing workflow code
});

Whatever is returned from the memoized function is cached and can be used by subsequent steps in the workflow, or multiple executions of the same workflow, provided the executionId is the same.

They are also visible in the workflow timeline.

Semantics

  • Memoized results are cached, and scoped to an executionId.
  • The memoized function will return a result.
  • Any errors thrown inside the memoized function are returned directly to the workflow.
    • If your intent is to fail the workflow as a result of a side-effect, you can let the error bubble up.
    • If your intent is to continue the workflow despite the error, you must catch the error and continue.