TemporalでSubscriptionのWorkflowを組む(実行中のWorkflowへのSignal送信編)
前回からの続きとなります。
- ✅ユーザーがサインアップしたらウェルカムメッセージ送信し、無料トライアル期間を
TrialPeriod
の間有効化する - ✅
TrialPeriod
が終わったら、課金処理を開始する- ❌無料トライアル期間内にユーザーがキャンセルしたらキャンセルメールを送信する
- 課金処理
-
MaxBillingPeriods
を超えていない限り -
BillingPeriodChargeAmound
を顧客に請求する - 2が終わったら
BillingPeriod
待機する - 待機中に顧客がキャンセルをしたら、サブスクリプションキャンセルメールを送信する
- サブスクリプションが完了(
MaxBillingPeriods
を超えた)したら、サブスクリプション終了メールを送信する
-
- サブスクリプション購読中の任意のタイミングで顧客に関する以下の情報が取得可能とする
- 請求金額
- 現在のサブスクリプション期間(手動での調整用 e.g. 返金など)
本記事では「無料トライアル期間内にユーザーがキャンセルしたらキャンセルメールを送信する」をどのように実現するのかを見ていきます。
Signalの活用
動作中のワークフローにデータを何かしら送りたい場合に使うのがSignalになります。
Signalを使うには以下2つが必要:
- Workflow側でハンドラーを実装
- Client側から呼び出す実装
Signalの実装
Workflowの改修
まずはWorkflow側の実装を入れます。キャンセルのメール送信用のアクティビティを作っておきます(これまた console.log
)。
export async function sendCancellationEmailDuringTrialPeriod(email: string) {
console.log(`Sending trial cancellation email to ${email}`);
}
そしてWorkflowでSignalのハンドラを作りつつ、キャンセルメール送信のActivityも呼び出すようにコードを変更。
-import { sleep, proxyActivities } from '@temporalio/workflow';
+import * as wf from '@temporalio/workflow';
import type * as activities from './activities';
-const { sendWelcomeEmail, sendSubscriptionOverEmail } = proxyActivities<typeof activities>({
+// actsでまとめて変数に入れるように変更
+const acts = wf.proxyActivities<typeof activities>({
startToCloseTimeout: '1 minute',
});
// Signalの宣言
+export const cancelSubscription = wf.defineSignal('cancelSignal');
+
export async function subscriptionWorkflow(email: string, trialPeriod: string | number): Promise<void> {
- await sendWelcomeEmail(email);
- await sleep(trialPeriod);
- await sendSubscriptionOverEmail(email);
+ let isCanceled = false; // キャンセルをトラックするための変数
+ wf.setHandler(cancelSubscription, () => void (isCanceled = true)); // ハンドラでキャンセルの状態を更新
+
+ await acts.sendWelcomeEmail(email);
+ await wf.sleep(trialPeriod);
+ // キャンセルの状態によって条件分岐させる
+ if (isCanceled) {
+ await acts.sendCancellationEmailDuringTrialPeriod(email);
+ } else {
+ await acts.sendSubscriptionOverEmail(email);
+ }
}
Client側からキャンセルする実装
ではClient側からキャンセルしてみます。本来これをサーバーサイドなどで実装するのですが、簡易的にnpm scriptsでこちらも実装します。
async function run() {
const connection = await Connection.connect({ address: 'temporal:7233' });
const workflowId = argv[2]; // コマンドライン引数からWorkflowIDを取得
const client = new WorkflowClient({ connection });
const handle = await client.getHandle(workflowId); // workflowのハンドラを取得
await handle.signal(cancelSubscription); // キャンセル用のSignalを送信
}
Workflowの実行
では、実行してみます。まずはWorkflowの実行まで前回同様に行います。
# workerの起動
npm run start.watch
# workflowの開始
npm run workflow
# Started workflow workflow-w2WoZy1WH5fvDamaNZ_sd
これで前回同様にWorkflowが開始されます。Client側のログに出力しているworkflowIdをコピーしておきます「workflow-w2WoZy1WH5fvDamaNZ_sd」。
# worker側のログ
Sending welcome email to hoge@example.com
では、30秒以内に先程作成したキャンセル用のスクリプトも実行してみます。このとき先程取得したWorkflowIDも渡します。
npm run workflow:cancel -- workflow-w2WoZy1WH5fvDamaNZ_sd
# "workflow:cancel": "ts-node src/scripts/cancelSubscription.ts"
すると、どうやらキャンセルがうまくいってそうなログが出力されます。
cancelling workflow: workflow-w2WoZy1WH5fvDamaNZ_sd
そしてしばらくするとWorker側のログにキャンセルメールの送信ログが出力されています。
Sending trial cancellation email to hoge@example.com
めでたしめでたしと見せかけて、まだイケてないところがあります。それは「キャンセルしたにもかかわらず sleep
が終わるまでキャンセルメールが送信されなかった」点です。バグってますね。
ということでこれを condition
を使って直していきましょう。
condition
でタイムアウト制御
sleep
は必ず指定した時間待機することになります。なので今回のユースケースには向いていません。そこで使えるのが condition
になります。 condition
は、中に書いている評価が true
になるまで待機できる機能です。そして、第2引数にはタイムアウトも指定することができる優れもの。
wf.setHandler(cancelSubscription, () => void (isCanceled = true));
await acts.sendWelcomeEmail(email);
- await wf.sleep(trialPeriod);
- if (isCanceled) {
+ if (await wf.condition(() => isCanceled, trialPeriod)) {
await acts.sendCancellationEmailDuringTrialPeriod(email);
} else {
await acts.sendSubscriptionOverEmail(email);
上記のコードで「キャンセルされるか、無料トライアル期間が終われば」次の処理に遷移します。
再実行すると、 npm run workflow:cancel
した直後にちゃんとキャンセルメールが送信されるのが確認できます。
おわりに
これで「無料トライアル期間内にユーザーがキャンセルしたらキャンセルメールを送信する」が実装できました。Signalを使って実行中のWorkflowにメッセージを送信する方法がわかりました。強力ですね。本記事はここまでとして、続きはまた次回。
現在の実装状況
- ✅ユーザーがサインアップしたらウェルカムメッセージ送信し、無料トライアル期間を
TrialPeriod
の間有効化する - ✅
TrialPeriod
が終わったら、課金処理を開始する- ✅無料トライアル期間内にユーザーがキャンセルしたらキャンセルメールを送信する
- 課金処理
-
MaxBillingPeriods
を超えていない限り -
BillingPeriodChargeAmound
を顧客に請求する - 2が終わったら
BillingPeriod
待機する - 待機中に顧客がキャンセルをしたら、サブスクリプションキャンセルメールを送信する
- サブスクリプションが完了(
MaxBillingPeriods
を超えた)したら、サブスクリプション終了メールを送信する
-
- サブスクリプション購読中の任意のタイミングで顧客に関する以下の情報が取得可能とする
- 請求金額
- 現在のサブスクリプション期間(手動での調整用 e.g. 返金など)
このデモを試したい人は以下のリポジトリでVSCodeのdevcontainerを用意しているので、うまくいけばそのまま動作確認できます。
続き
Discussion