Build A Subscription Workflow With Temporal and TypeScript - Learn Temporal
Build A Subscription Workflow With Temporal and TypeScript - Learn Temporal
WORK IN PROGRESS
This tutorial is a work in progress. Some sections may be incomplete, out of date, or
missing. We're working to update it.
Introduction
In this tutorial, you will tour all of the Workflow APIs you should know, primarily Signals,
Queries, condition , and sleep (and eventually Child Workflows and continueAsNew ), by
building a realistic monthly subscription payments workflow that can be canceled and changed
while it runs.
The goal is to give you a more accessible introduction to these APIs by explaining them in
context of a realistic application.
We also give an example of how you break down project requirements into Activities and
Workflow logic.
Your task is to write a Workflow for a limited time Subscription (eg a 36 month Phone plan) that
satisfies these conditions:
1 When the user signs up send a welcome email and start a free trial for TrialPeriod
https://learn.temporal.io/tutorials/typescript/subscriptions/ 1/10
1. When the user signs up, send a welcome email and start a free trial for TrialPeriod .
02/08/2023, 06:51 Build a subscription workflow with Temporal and TypeScript | Learn Temporal
Prerequisites
Set up a local development environment for developing Temporal applications using
TypeScript
Review the Hello World in TypeScript tutorial to understand the basics of getting a
Temporal TypeScript SDK project up and running.
We don't assume knowledge of the Workflow APIs, but we do expect that you are reasonably
comfortable with TypeScript/Node.js.
https://learn.temporal.io/tutorials/typescript/subscriptions/ 2/10
02/08/2023, 06:51 Build a subscription workflow with Temporal and TypeScript | Learn Temporal
We'll start out this project with the Hello World example, which you can always scaffold out
with our Package Initializer:
$ npx @temporalio/create@latest subscription-tutorial --sample hello-
world
// /src/workflows.ts
import { sleep, proxyActivities } from '@temporalio/workflow';
import type * as activities from './activities';
await sleep(trialPeriod);
await sendSubscriptionOverEmail(email);
}
Here we are using the sleep API for the first time. It does what it says; defers execution for a
preset time (note that it accepts both a string or number of milliseconds, per its docs).
To test this out, you will also have to modify your Client code accordingly:
TypeScript JavaScript
// /src/client.ts
import { SubscriptionWorkflow } from './workflows';
// etc...
const result = await client.execute(SubscriptionWorkflow, {
workflowId: 'business-meaningful-id',
taskQueue: 'tutorial',
args: ['[email protected]', '30 seconds'],
});
Notice that the args are typechecked against the signature of SubscriptionWorkflow . We
also take the opportunity to specify the workflowId , which we recommend in production.
Check that you can run the Workflow ( npm run workflow ) and everything works as expected
(if it is still printing "Hello Temporal", make sure you restart your Workers to pick up changes in
your code, which you can automate with npm run start.watch ).
From here on we expect that you are comfortable with making changes to Workflows and
rerunning them to verify that they work as expected after each change.
THE IMPORTANCE OF DEFERRED EXECUTION
sleep is a simple, but powerful durable timer; under the hood, Temporal Server is
persisting the sleep details to the database to be picked up later by the internal scheduler.
Test the fault tolerance of this by intentionally bringing down your Worker, or Temporal
Server, during the sleep , and notice that it simply continues when the system is back up
again.
https://learn.temporal.io/tutorials/typescript/subscriptions/ 4/10
02/08/2023, 06:51 Build a subscription workflow with Temporal and TypeScript | Learn Temporal
First we have to define the Signal, then set a handler for it. The import code is starting to get a
little verbose, so there are some optional couple of quality-of-life refactors you can do as well:
TypeScript JavaScript
// /src/workflows.ts
import * as wf from '@temporalio/workflow'; // don't need to import
everything
// import activity types
}
}
To test your new Signal, you will need to write a new script that has a Client. Copy over most of
the code from your original starter to cancel-subscription.ts .
To invoke the Signal from here, we have to get a handle to the running Workflow Execution, and
then signal it:
TypeScript JavaScript
// cancel-subscription.ts
import { cancelSubscription } from '../workflows';
// ...
When you run this script while the main client script is running, you should be able to see
the Signal registered in the Event History when you pull up the Workflow on Temporal Web.
More importantly, there is a small bug with this code - the cancelation doesn't happen
immediately when you cancel!
await acts.sendWelcomeEmail(email);
await wf.condition(() => isCanceled === true); // new! wait until
isCanceled = true
await acts.sendCancellationEmailDuringTrialPeriod(email); // arrive
here immediately after cancellation
However, we need to Promise.race this against the Trial Period to fulfil Requirement 2. This
is such a common need that we built it in as condition 's optional timeout argument:
TypeScript JavaScript
// /src/workflows.ts
// ...
export async function SubscriptionWorkflow(
email: string,
trialPeriod: string | number
) {
let isCanceled = false;
wf.setHandler(cancelSignal, () => void (isCanceled = true)); // new
await acts.sendWelcomeEmail(email);
if (await wf.condition(() => isCanceled, trialPeriod)) {
// reach here if predicate function is true
await acts.sendCancellationEmailDuringTrialPeriod(email);
} else {
// reach here if timeout happens first
await acts.sendSubscriptionOverEmail(email);
}
}
Now when you run your cancel-subscription script you can see that the cancellation
happens immediately. And now you know the basics of writing asynchronous time- and Signal-
based logic. We have documented other patterns you are likely to use, like sleepUntil and
Updatable Timers.
Data Model
We are about to start charging for money and adding more settable properties like
BillingPeriod and BillingPeriodChargeAmount . It's time to evolve the data model
accordingly. We'll define a shared type:
https://learn.temporal.io/tutorials/typescript/subscriptions/ 7/10
02/08/2023, 06:51 Build a subscription workflow with Temporal and TypeScript | Learn Temporal
TypeScript JavaScript
And refactor our existing Workflow and Client code accordingly. We'll spare you the gory
details - you can always check our repo if you like, but there is no right answer here.
TypeScript JavaScript
// inside Workflow
let amountCharged = customer.initialBillingPeriodCharge;
wf.setHandler(amountChargedQuery, () => amountCharged);
wf.setHandler(
updateAmountCharged,
(newAmount: number) => void (amountCharged = newAmount)
);
// do stuff with amountCharged
You can set up Signals and Queries and Handlers for every field, or perhaps just one set for the
entire Customer object, it depends on your needs. But these primitives can be flexibly
rearranged (with specific inspiration from React Custom Hooks) to reduce boilerplate for your
needs, as we have documented.
Knowledge check time - Write scripts with Temporal Clients for:
making the queries
signalling updates in charge amount
You can always refer to our repo if you get stuck.
Note that Signal handlers cannot return data, and Query handlers must not mutate state.
These restrictions and other notes on type safety are prominently noted in their
documentation.
End Result
Next Steps
https://learn.temporal.io/tutorials/typescript/subscriptions/ 9/10
02/08/2023, 06:51 Build a subscription workflow with Temporal and TypeScript | Learn Temporal
You should now be able to write a Workflow that uses Signals, Queries, sleep , and
condition interchangeably and confidently. These are meant to be low level primitives, and it
is entirely expected that you will build up reusable functions and libraries with APIs you prefer
as you proceed.
Two paths from here:
Go Full Stack: Integrate the manually-run Temporal Client scripts you have written here
into an Express.js app, or serverless function. Our Next.js Tutorial should help show you
how to integrate this with a frontend app, and give indications on how to deploy.
Learn More: Explore using Child Workflows and continueAsNew so that your
subscriptions can keep running indefinitely.
https://learn.temporal.io/tutorials/typescript/subscriptions/ 10/10