将 Durable Functions 应用迁移到 Node.js 编程模型版本 4

本文提供了将现有 Durable Functions 应用升级到 Node.js 编程模型版本 4 的指南。 请注意,本文使用“TIP”横幅来总结升级应用所需的关键步骤。

如果你对创建全新的 v4 应用感兴趣,则可以遵循适用于 JavaScriptTypeScript 的 Visual Studio Code 快速入门。

提示

在遵循本指南之前,请确保遵循常规版本 4 升级指南

先决条件

在遵循本指南之前,请确保先执行以下步骤:

升级 durable-functions npm 包

备注

编程模型版本不应与 durable-functions 包版本混淆。 v4 编程模型需要 durable-functions 包版本 3.x,而 v3 编程模型需要 durable-functions 版本 2.x。

durable-functions npm 包的 v3.x 支持 v4 编程模型。 在编程模型 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 命名空间上的方法注册 Durable Functions:df.app.orchestration()df.app.entity()df.app.activity()

注册 Durable 客户端输入绑定

在 v4 模型中,注册辅助输入绑定(如 Durable 客户端)也是在代码中完成的! 使用 input.durableClient() 方法将 Durable 客户端输入绑定注册到所选函数。 在函数正文中,使用 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() 方法将持久客户端额外输入注册到客户端函数。 照常使用 getClient() 检索 DurableClient 实例。

更新持久客户端 API 调用

durable-functionsv3.x 中,DurableClient 类(从 DurableOrchestrationClient 重命名)上的多个 API 已被简化,以使调用它们更容易、更精简。 对于 API 的许多可选参数,现在传递一个选项对象,而不是多个离散的可选参数。 下面是这些更改的示例:

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 模型l (durable-functions v2.x) V4 模型l (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。 进行了以下更改:

  • 接受所有参数的一个选项对象,而不是多个可选参数,以更类似于框架(如 Express)。
  • uri 参数重命名为 url
  • content 参数重命名为 body
  • 弃用 asynchronousPatternEnabled 标志,转而改用 enablePolling

如果业务流程使用了 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
});

提示

更新业务流程中对 callHttp 的 API 调用,以使用新的选项对象。

利用新类型

durable-functions 包现在会公开以前未导出的新类型! 这使你可以更强地类型化函数,并为业务流程、实体和活动提供更强的类型安全性! 这也改进了用于创作这些函数的 IntelliSense。

下面是一些新的导出类型:

  • OrchestrationHandlerOrchestrationContext(用于业务流程)
  • EntityHandlerEntityContext(用于实体)
  • 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 存储库中提交 bug 报告。