次の方法で共有


Durable Functions アプリを Node.js プログラミング モデルのバージョン 4 に移行する

この記事では、既存の Durable Functions アプリを Node.js プログラミング モデルのバージョン 4 にアップグレードするためのガイドを提供します。 この記事では、"ヒント" バナーを使用して、アプリのアップグレードに必要な主要な手順を要約します。

代わりに、新規の v4 アプリの作成に関心がある場合は、JavaScriptTypeScript の Visual Studio Code のクイックスタートに従うことができます。

ヒント

このガイドに従う前に、一般的なバージョン 4 のアップグレード ガイドに従っていることを確認してください。

前提条件

このガイドに従う前に、まず次の手順に従ってください。

durable-functions npm パッケージをアップグレードする

Note

プログラミング モデルのバージョンと durable-functions パッケージのバージョンを混同しないでください。 v4 プログラミング モデルには durable-functions パッケージ バージョン 3.x が必要ですが、v3 プログラミング モデルには durable-functions バージョン 2.x が必要です。

v4 プログラミング モデルは、durable-functions npm パッケージの v3.x でサポートされています。 プログラミング モデル v3 アプリでは、依存関係に durable-functions v2.x が一覧表示されている可能性があります。 durable-functions パッケージの v3.x に更新したことを確認してください。

ヒント

durable-functions npm パッケージの v3.x にアップグレードします。 これを行うには、次のコマンドを使います。

npm install durable-functions

Durable Functions トリガーを登録する

v4 プログラミング モデルでは、トリガーとバインドを別 function.json のファイルで宣言することは過去の処理です。 durable-functions パッケージのルートにある app 名前空間にある新しい API を使用して、Durable Functions のトリガーとバインドをコードで直接登録できるようになりました。 例については、以下のコード スニペットを参照してください。

オーケストレーションの移行

const df = require('durable-functions');

const activityName = 'helloActivity';

df.app.orchestration('durableOrchestrator', function* (context) {
    const outputs = [];
    outputs.push(yield context.df.callActivity(activityName, 'Tokyo'));
    outputs.push(yield context.df.callActivity(activityName, 'Seattle'));
    outputs.push(yield context.df.callActivity(activityName, 'Cairo'));

    return outputs;
});
import * as df from 'durable-functions';
import { OrchestrationContext, OrchestrationHandler } from 'durable-functions';

const activityName = 'hello';

const durableHello1Orchestrator: OrchestrationHandler = function* (context: OrchestrationContext) {
    const outputs = [];
    outputs.push(yield context.df.callActivity(activityName, 'Tokyo'));
    outputs.push(yield context.df.callActivity(activityName, 'Seattle'));
    outputs.push(yield context.df.callActivity(activityName, 'Cairo'));

    return outputs;
};
df.app.orchestration('durableOrchestrator', durableHello1Orchestrator);

エンティティの移行

const df = require('durable-functions');

df.app.entity('Counter', (context) => {
    const currentValue = context.df.getState(() => 0);
    switch (context.df.operationName) {
        case 'add':
            const amount = context.df.getInput();
            context.df.setState(currentValue + amount);
            break;
        case 'reset':
            context.df.setState(0);
            break;
        case 'get':
            context.df.return(currentValue);
            break;
    }
});
import * as df from 'durable-functions';
import { EntityContext, EntityHandler } from 'durable-functions';

const counterEntity: EntityHandler<number> = (context: EntityContext<number>) => {
    const currentValue: number = context.df.getState(() => 0);
    switch (context.df.operationName) {
        case 'add':
            const amount: number = context.df.getInput();
            context.df.setState(currentValue + amount);
            break;
        case 'reset':
            context.df.setState(0);
            break;
        case 'get':
            context.df.return(currentValue);
            break;
    }
};
df.app.entity('Counter', counterEntity);

アクティビティの移行

const df = require('durable-functions');

df.app.activity('hello', {
    handler: (input) => {
        return `Hello, ${input}`;
    },
});
import * as df from 'durable-functions';
import { ActivityHandler } from "durable-functions";

const helloActivity: ActivityHandler = (input: string): string => {
    return `Hello, ${input}`;
};

df.app.activity('hello', { handler: helloActivity });

ヒント

Durable Functions アプリから function.json ファイルを削除します。 代わりに、app 名前空間の メソッド (df.app.orchestration()df.app.entity()df.app.activity()) を使用して Durable Functions を登録します。

Durable Client 入力バインディングを登録する

v4 モデルでは、永続的なクライアントと同様に、セカンダリ入力バインディングの登録もコードで行われます。 input.durableClient() メソッドを使用して、永続的なクライアント入力バインディングを任意の関数に登録します。 関数の本体で、getClient() を使用して、以前と同様にクライアント インスタンスを取得します。 次の例は、HTTP によってトリガーされる関数を使用する例を示しています。

const { app } = require('@azure/functions');
const df = require('durable-functions');

app.http('durableHttpStart', {
    route: 'orchestrators/{orchestratorName}',
    extraInputs: [df.input.durableClient()],
    handler: async (_request, context) => {
        const client = df.getClient(context);
        // Use client in function body
    },
});
import { app, HttpHandler, HttpRequest, HttpResponse, InvocationContext } from '@azure/functions';
import * as df from 'durable-functions';

const durableHttpStart: HttpHandler = async (request: HttpRequest, context: InvocationContext): Promise<HttpResponse> => {
    const client = df.getClient(context);
    // Use client in function body
};

app.http('durableHttpStart', {
    route: 'orchestrators/{orchestratorName}',
    extraInputs: [df.input.durableClient()],
    handler: durableHttpStart,
});

ヒント

input.durableClient() メソッドを使用して、永続的なクライアントの追加入力をクライアント関数に登録します。 DurableClient インスタンスを取得するには、通常どおりに getClient() を使用します。

Durable Client API 呼び出しを更新する

durable-functionsv3.x では、DurableClient クラス上の複数の API (DurableOrchestrationClient から名前が変更) が簡略化され、呼び出しが容易になり、効率化されました。 多くのオプションの引数を API に渡す場合、複数の個別のオプションの引数ではなく、1 つのオプション オブジェクトを渡すようになりました。 これらの変更の例を次に示します。

const client = df.getClient(context)
const status = await client.getStatus('instanceId', {
    showHistory: false,
    showHistoryOutput: false,
    showInput: true
});
const client: DurableClient = df.getClient(context);
const status: DurableOrchestrationStatus = await client.getStatus('instanceId', {
    showHistory: false,
    showHistoryOutput: false,
    showInput: true
});

すべての変更のリストについては、以下を参照してください。

V3 モデル (Durable Functions v2.x) V4 モデル (Durable Functions v3.x)
getStatus(
    instanceId: string,
    showHistory?: boolean,
    showHistoryOutput?: boolean,
    showInput?: boolean
): Promise<DurableOrchestrationStatus>
getStatus(
    instanceId: string, 
    options?: GetStatusOptions
): Promise<DurableOrchestrationStatus>
getStatusBy(
    createdTimeFrom: Date | undefined,
    createdTimeTo: Date | undefined,
    runtimeStatus: OrchestrationRuntimeStatus[]
): Promise<DurableOrchestrationStatus[]>
getStatusBy(
    options: OrchestrationFilter
): Promise<DurableOrchestrationStatus[]>
purgeInstanceHistoryBy(
    createdTimeFrom: Date,
    createdTimeTo?: Date,
    runtimeStatus?: OrchestrationRuntimeStatus[]
): Promise<PurgeHistoryResult>
purgeInstanceHistoryBy(
    options: OrchestrationFilter
): Promise<PurgeHistoryResult>
raiseEvent(
    instanceId: string,
    eventName: string,
    eventData: unknown,
    taskHubName?: string,
    connectionName?: string
): Promise<void>
raiseEvent(
    instanceId: string,
    eventName: string,
    eventData: unknown,
    options?: TaskHubOptions
): Promise<void>
readEntityState<T>(
    entityId: EntityId,
    taskHubName?: string,
    connectionName?: string
): Promise<EntityStateResponse<T>>
readEntityState<T>(
    entityId: EntityId,
    options?: TaskHubOptions
): Promise<EntityStateResponse<T>>
rewind(
    instanceId: string,
    reason: string,
    taskHubName?: string,
    connectionName?: string
): Promise<void>`
rewind(
    instanceId: string, 
    reason: string, 
    options?: TaskHubOptions
): Promise<void>
signalEntity(
    entityId: EntityId,
    operationName?: string,
    operationContent?: unknown,
    taskHubName?: string,
    connectionName?: string
): Promise<void>
signalEntity(
    entityId: EntityId, 
    operationName?: string,
    operationContent?: unknown,
    options?: TaskHubOptions
): Promise<void>
startNew(
    orchestratorFunctionName: string,
    instanceId?: string,
    input?: unknown
): Promise<string>
startNew(
    orchestratorFunctionName: string, 
    options?: StartNewOptions
): Promise<string>;
waitForCompletionOrCreateCheckStatusResponse(
    request: HttpRequest,
    instanceId: string,
    timeoutInMilliseconds?: number,
    retryIntervalInMilliseconds?: number
): Promise<HttpResponse>;
waitForCompletionOrCreateCheckStatusResponse(
    request: HttpRequest,
    instanceId: string,
    waitOptions?: WaitForCompletionOptions
): Promise<HttpResponse>;

ヒント

必要に応じて、DurableClient API 呼び出しを個別のオプションの引数からオプション オブジェクトに更新してください。 影響を受けるすべての API については、上記のリストを参照してください。

callHttp API 呼び出しを更新する

durable-functions の v3.x では、DurableOrchestrationContextcallHttp() API が更新されました。 変更点を次に示します。

  • 複数のオプションの引数ではなく、すべての引数に対して 1 つのオプション オブジェクトを受け入れます。これは Express などのフレームワークに近くなっています。
  • uri 引数の名前を url に変更します。
  • content 引数の名前を body に変更します。
  • enablePolling を優先して asynchronousPatternEnabled フラグを非推奨にしました。

オーケストレーションで callHttp API を使用していた場合は、上記の変更に準拠するようにこれらの API 呼び出しを更新してください。 次の例をご覧ください:

const restartResponse = yield context.df.callHttp({
    method: "POST",
    url: `https://example.com`,
    body: "body",
    enablePolling: false
});
const restartResponse = yield context.df.callHttp({
    method: "POST",
    url: `https://example.com`,
    body: "body",
    enablePolling: false
});

ヒント

新しいオプション オブジェクトを使用するように、オーケストレーション内で API 呼び出しを callHttp に更新します。

新しい型を活用する

durable-functions パッケージで、以前にエクスポートされなかった新しい型が公開されるようになりました。 これにより、関数をより厳密に型指定し、オーケストレーション、エンティティ、およびアクティビティのより強力なタイプ セーフを提供できます。 これにより、これらの関数を作成するための IntelliSense も向上します。

新しくエクスポートされた型の一部を次に示します。

  • オーケストレーション用の OrchestrationHandler および OrchestrationContext
  • エンティティ用の EntityHandler および EntityContext
  • アクティビティ用の ActivityHandler
  • クライアント関数用の DurableClient クラス

ヒント

durable-functions パッケージからエクスポートされた新しい型を利用して、関数を厳密に型指定します。

トラブルシューティング

オーケストレーション コードの実行時に次のエラーが表示される場合は、Azure Functions ランタイムv4.25 以上を実行しているか、ローカルで実行されている場合は、Azure Functions Core Toolsv4.0.5382 以上を実行していることを確認してください。

Exception: The orchestrator can not execute without an OrchestratorStarted event.
Stack: TypeError: The orchestrator can not execute without an OrchestratorStarted event.

それでも正常に機能しない場合、または他の問題が発生した場合は、いつでも GitHub リポジトリにバグ レポートを提出できます。