Durable Functions での外部イベントの処理をTypescriptで試してみたメモ

# 概要
オーケストラ関数の公式の手順を試してみたメモ。

[ソースコード](https://github.com/hibohiboo/async-ttrpg/blob/546ada5b688aa2cd5c20d0e1d0128c959e11d002/apps/api/src/functions/durableHello1.ts)

```ts
df.app.orchestration(
  'durableOrchestratorOrchestratorFunctionCodeConstraints',
  function* durableOrchestratorOrchestratorFunctionCodeConstraints(context) {
    const outputs: string[] = [];

    // GUID
    outputs.push(context.df.newGuid('v5なので与えられた文字列に対して一定'));
    context.log('uuid v5.', outputs);

    // 日付と時刻
    const expiration = addSeconds(context.df.currentUtcDateTime, 30);
    const timeoutTask = context.df.createTimer(expiration); // 30秒待ち
    const approved = context.df.waitForExternalEvent('Approval'); // 承認イベント待ち
    context.log('wait approve start');

    // どちらかが終わるまで待つ = 承認が30秒でされなければタイムアウトの動きとなる
    const winner = yield context.df.Task.any([timeoutTask, approved]);
    if (winner === timeoutTask) {
      outputs.push('timeout');
    } else {
      outputs.push('not timeout');
    }
    context.log('Orchestrator end.', outputs);
    return outputs;
  },
);
```

このDurableFunctionsをHTTPトリガーで起動するとレスポンスからイベント発火用のエンドポイントを取得できる。
```
get http://127.0.0.1:7071/orchestrators/durableOrchestratorOrchestratorFunctionCodeConstraints
```
レスポンスで下記が返ってくる

```diff
HTTP/1.1 202 Accepted
Connection: close
Content-Type: application/json
Date: Sun, 17 Nov 2024 23:02:57 GMT
Server: Kestrel
Location: http://127.0.0.1:7071/runtime/webhooks/durabletask/instances/13cce035d9814c1b91d1b7328cd6f21c?taskHub=TestHubName&connection=Storage&code=_Ddz0VTX4e1JOKn6D-JbReCyjujHleUaiwWYavNjLidYAzFuMyUTZQ==
Retry-After: 10
Transfer-Encoding: chunked
x-ms-invocation-id: cd5afc4b-db10-4a45-b647-3fd6b91de318

{
  "id": "13cce035d9814c1b91d1b7328cd6f21c",
  "statusQueryGetUri": "http://127.0.0.1:7071/runtime/webhooks/durabletask/instances/13cce035d9814c1b91d1b7328cd6f21c?taskHub=TestHubName&connection=Storage&code=_Ddz0VTX4e1JOKn6D-JbReCyjujHleUaiwWYavNjLidYAzFuMyUTZQ==",
+  "sendEventPostUri": "http://127.0.0.1:7071/runtime/webhooks/durabletask/instances/13cce035d9814c1b91d1b7328cd6f21c/raiseEvent/{eventName}?taskHub=TestHubName&connection=Storage&code=_Ddz0VTX4e1JOKn6D-JbReCyjujHleUaiwWYavNjLidYAzFuMyUTZQ==",
  "terminatePostUri": "http://127.0.0.1:7071/runtime/webhooks/durabletask/instances/13cce035d9814c1b91d1b7328cd6f21c/terminate?reason={text}&taskHub=TestHubName&connection=Storage&code=_Ddz0VTX4e1JOKn6D-JbReCyjujHleUaiwWYavNjLidYAzFuMyUTZQ==",
  "rewindPostUri": "http://127.0.0.1:7071/runtime/webhooks/durabletask/instances/13cce035d9814c1b91d1b7328cd6f21c/rewind?reason={text}&taskHub=TestHubName&connection=Storage&code=_Ddz0VTX4e1JOKn6D-JbReCyjujHleUaiwWYavNjLidYAzFuMyUTZQ==",
  "purgeHistoryDeleteUri": "http://127.0.0.1:7071/runtime/webhooks/durabletask/instances/13cce035d9814c1b91d1b7328cd6f21c?taskHub=TestHubName&connection=Storage&code=_Ddz0VTX4e1JOKn6D-JbReCyjujHleUaiwWYavNjLidYAzFuMyUTZQ==",
  "restartPostUri": "http://127.0.0.1:7071/runtime/webhooks/durabletask/instances/13cce035d9814c1b91d1b7328cd6f21c/restart?taskHub=TestHubName&connection=Storage&code=_Ddz0VTX4e1JOKn6D-JbReCyjujHleUaiwWYavNjLidYAzFuMyUTZQ==",
  "suspendPostUri": "http://127.0.0.1:7071/runtime/webhooks/durabletask/instances/13cce035d9814c1b91d1b7328cd6f21c/suspend?reason={text}&taskHub=TestHubName&connection=Storage&code=_Ddz0VTX4e1JOKn6D-JbReCyjujHleUaiwWYavNjLidYAzFuMyUTZQ==",
  "resumePostUri": "http://127.0.0.1:7071/runtime/webhooks/durabletask/instances/13cce035d9814c1b91d1b7328cd6f21c/resume?reason={text}&taskHub=TestHubName&connection=Storage&code=_Ddz0VTX4e1JOKn6D-JbReCyjujHleUaiwWYavNjLidYAzFuMyUTZQ=="
}
```

返ってきた**sendEventPostUrl**の`{EventName}`を任意のイベント名(今回は `Approval`)にすることでイベントを発火できる。

```
POST http://127.0.0.1:7071/runtime/webhooks/durabletask/instances/489d9ecc16e94197854a7abfd7c648a2/raiseEvent/Approval?taskHub=TestHubName&connection=Storage&code=_Ddz0VTX4e1JOKn6D-JbReCyjujHleUaiwWYavNjLidYAzFuMyUTZQ==
Content-Type: application/json

"true"
```

なお、インスタンスIDを間違えると`410`のレスポンスが返ることとなる。

今回の例だと、３０秒以内にイベントを発火させた場合に下記のログが表示される。

```
[2024-11-17T23:02:35.387Z] Orchestrator end. ['6e0ef2f1-488f-5071-b477-1c73254c8bbc', 'not timeout']
```

newGUIDで発行されるuuidはv5のため、入力の文字列が変わらなければ一定の結果を返す。

# 参考
[Durable Functions での外部イベントの処理 (Azure Functions)](https://learn.microsoft.com/ja-jp/azure/azure-functions/durable/durable-functions-external-events?tabs=python)
[オーケストレーター関数コードの制約](https://learn.microsoft.com/ja-jp/azure/azure-functions/durable/durable-functions-code-constraints?tabs=csharp)