TemporalでSubscriptionのWorkflowを組む(初めてのWorkflow実行編)
Temporalとは
TemporalはWorkflow Engineなのですが、日本語での簡単な紹介は以下の記事で書いたことがあります。
AWSのStep Functionsを知っている方は、それとジャンルは似たものと思ってOKかと思います(とても雑ですが)。
公式サイトは以下
SubscriptionのWorkflowを組んで見る
Temporalにはサンプルコードがたくさんあるのですが、その中でもこの記事では「Build a subscription workflow with Temporal and TypeScript」をWalkthroughしていきたいと思います。
ワークフローの概要
組みたいワークフローとしては以下のとおり
- ユーザーがサインアップしたらウェルカムメッセージ送信し、無料トライアル期間を
TrialPeriod
の間有効化する -
TrialPeriod
が終わったら、課金処理を開始する- 無料トライアル期間内にユーザーがキャンセルしたらキャンセルメールを送信する
- 課金処理
-
MaxBillingPeriods
を超えていない限り -
BillingPeriodChargeAmound
を顧客に請求する - 2が終わったら
BillingPeriod
待機する - 待機中に顧客がキャンセルをしたら、サブスクリプションキャンセルメールを送信する
- サブスクリプションが完了(
MaxBillingPeriods
を超えた)したら、サブスクリプション終了メールを送信する
-
- サブスクリプション購読中の任意のタイミングで顧客に関する以下の情報が取得可能とする
- 請求した金額
- 現在のサブスクリプション期間(手動での調整用 e.g. 返金など)
プロジェクトの作成
hello-world
のサンプルをベースに作っていく。
npx @temporalio/create@latest subscription-tutorial --sample hello-world
Activityの作成
ワークフローの黒字にあたる部分が外界に対してのアクション(顧客への請求、メール送信)なので、TemporalのActivityとして実装します。
実際のロジックは組まずに console.log
だけで「やったこと」にします。まずはウェルカムメッセージのメール送信とサブスクリプションについてのメール送信を作ります。
export async function sendWelcomeEmail(email: string) {
console.log(`Sending welcome email to ${email}`);
}
export async function sendSubscriptionOverEmail(email: string) {
console.log(`Sending subscription over email to ${email}`);
}
sleep
で実装
無料トライアルのワークフローを では、上で作ったActivityを使ってワークフローを組みます。 @temporalio/workflow
が用意してくれる sleep
を使って、ウェルカムメッセージから trialPeriod
の間待ってから、サブスクリプション完了のメールを送信します。
import { sleep, proxyActivities } from '@temporalio/workflow';
import type * as activities from './activities';
const { sendWelcomeEmail, sendSubscriptionOverEmail } = proxyActivities<typeof activities>({
startToCloseTimeout: '1 minute',
});
export async function subscriptionWorkflow(email: string, trialPeriod: string | number): Promise<void> {
await sendWelcomeEmail(email);
await sleep(trialPeriod);
await sendSubscriptionOverEmail(email);
}
このワークフローを実行してみます。そのためには呼び出し側である client.ts
を以下のように書き換えます。
import { Connection, Client } from '@temporalio/client';
import { subscriptionWorkflow } from './workflows';
import { nanoid } from 'nanoid';
async function run() {
const connection = await Connection.connect({ address: 'temporal:7233' });
const client = new Client({ connection });
const handle = await client.workflow.start(subscriptionWorkflow, {
args: ['hoge@example.com', '30 seconds'],
taskQueue: 'subscription-tutorial',
// in practice, use a meaningful business ID, like customerId or transactionId
workflowId: 'workflow-' + nanoid(),
});
console.log(`Started workflow ${handle.workflowId}`);
// ...
}
// ...
});
ポイントとしてはClientから subscriptionWorkflow
を呼び出していて、引数として args
には ['hoge@example.com', '30 seconds']
を渡しているところです。ちゃんとこの args
は型も意識してくれるので、間違えると実装時に怒ってくれます。
Temporalで、ClientからのリクエストがWorkerに到達するイメージは以下のような感じです(たぶん)。このClientを、Node.jsだったらExpressだったり、Next.jsのサーバーサイドのコードから使うイメージです。
ワークフローの実行
では実際に実行してみます。まずはWorkerを起動しておきます。
npm run start.watch
# "start.watch": "nodemon src/worker.ts"
うまくいくとログに [INFO] Worker state changed { state: 'RUNNING' }
と表示されます。Workerの準備ができたので、Clientからワークフローの実行をします。
npm run workflow
# "workflow": "ts-node src/client.ts"
すると、以下のようにClient側でログが出力されます
Started workflow workflow-ooTIVjejtBtZJv6mQcqcV
そして、Worker側では以下のログが出力されます。メール送信処理が走っています。
Sending welcome email to hoge@example.com
ここでしばらく待機(無料期間は30秒に設定しています)。すると、30秒後に以下がWorker側のログに出力されます。
Sending subscription over email to hoge@example.com
簡単ですがワークフローの実行は正常に完了していることがわかります。
ワークフローの確認(UI)
Elasticsearchが立ち上がっていると、ワークフローの確認をUIから行うことができます。
下記が特にポイントとなるイベントですね。
- WorkflowExecutionStarted:
subscriptionWorkflow
- ActivityTaskScheduled:
sendWelcomeEmail
- TimerStarted:
30 seconds
- ActifityTaskScheduled:
sendSubscriptionOverEmail
- WorkflowExecutionCompleted
Step Functionsみたいに視覚的にも見えるようになったらさらに良さそうですね。
おわりに
ということでワークフローの1, 2に関して実行できるところまで確認できました。ただし、2の中の「 無料トライ合う期間内にユーザーがキャンセルしたらキャンセルメールを送信する」を実現できていないのはなかなか致命的です。続きについては次回やっていきましょう。
現在の実装状況
- ✅ユーザーがサインアップしたらウェルカムメッセージ送信し、無料トライアル期間を
TrialPeriod
の間有効化する - ✅
TrialPeriod
が終わったら、課金処理を開始する- ❌無料トライアル期間内にユーザーがキャンセルしたらキャンセルメールを送信する
- 課金処理
-
MaxBillingPeriods
を超えていない限り -
BillingPeriodChargeAmound
を顧客に請求する - 2が終わったら
BillingPeriod
待機する - 待機中に顧客がキャンセルをしたら、サブスクリプションキャンセルメールを送信する
- サブスクリプションが完了(
MaxBillingPeriods
を超えた)したら、サブスクリプション終了メールを送信する
-
- サブスクリプション購読中の任意のタイミングで顧客に関する以下の情報が取得可能とする
- 請求金額
- 現在のサブスクリプション期間(手動での調整用 e.g. 返金など)
このデモを試したい人は以下のリポジトリでVSCodeのdevcontainerを用意しているので、うまくいけばそのまま動作確認できます。
続き
Discussion