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

本指南介绍升级到 v4 Node.js 编程模型时所需的Durable Functions特定更改。 若要改为创建新的 v4 应用,请参阅 JavaScriptTypeScript 的入门指南。

Important

首先完成常规 Node.js v4 升级指南 。 本文仅介绍与Durable Functions相关的额外更改。

迁移清单

使用以下清单跟踪每个迁移步骤的进度:

Step 章节
1.验证先决条件 Prerequisites
2.升级 npm 包 升级 durable-functions npm 包
3. 将 function.json 替换为基于代码的注册 注册“Durable Functions”触发器
在代码中注册持久客户端 注册持久客户端输入绑定
5. 更新客户端 API 调用 更新您的 Durable Client API 调用
6. 更新 callHttp 调用 更新对 callHttp API 的调用
7. (TypeScript) 使用新的导出类型 使用新的导出类型增强类型安全性

先决条件

durable-functions升级 npm 包

编程模型版本和 durable-functions 包版本不同:

编程模型 durable-functions
v3 2.x
v4 3.x

升级到 v3.x 包:

npm install durable-functions

注册 Durable Functions 触发器

在 v4 模型中,不再在单独的 function.json 文件中声明触发器和绑定。 而是直接在代码中使用 df.app 命名空间注册 Durable Functions 触发器。 迁移每个函数后,删除其 function.json 文件。

函数类型 注册方法
Orchestration df.app.orchestration()
实体 df.app.entity()
Activity df.app.activity()

以下示例显示了每种函数类型的迁移模式。

编排

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 });

注册持久客户端输入绑定

在 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,
});

更新 Durable Client API 调用

多个 API 在 DurableClient(之前为 DurableOrchestrationClient)上现在接受一个选项对象,而不是多个可选参数。 最常见的受影响的 API 是 startNew ; getStatus如果只使用这些 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
});

下表列出了所有受影响的 API:

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>;

更新对 callHttp API 的调用

在 v3.x 的durable-functions中,callHttp() API 进行了以下更新:

更改 详细信息
参数→ 选项对象 所有参数现在都作为单个选项对象传递,类似于 Express
uriurl 已重命名以保持一致性。
contentbody 已重命名以保持一致性。
asynchronousPatternEnabledenablePolling 为了清楚起见,已重命名。

如果编排使用 callHttp,请将调用更新为新语法:

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
});

使用新的导出类型增强类型安全性

durable-functions 现在公开了以前未导出的新类型。 这些类型允许你更强地键入函数,并为业务流程、实体和活动提供更强的类型安全性。 它们还改进了用于创作这些函数的 IntelliSense。

以下列表包括一些新的导出类型:

  • OrchestrationHandlerOrchestrationContext编排
  • EntityHandlerEntityContext 实体
  • ActivityHandler 用于活动
  • DurableClient 客户端函数的类

故障排除

如果没有 OrchestratorStarted 事件,编排器无法执行。

如果看到以下错误,请确保运行至少v4.25Azure Functions运行时或至少v4.0.5382Azure Functions Core Tools(如果在本地运行)。

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

迁移后未发现函数

如果迁移后未显示函数,请验证:

  • 删除或重命名了旧 function.json 文件。 剩余 function.json 文件可能会与基于代码的注册冲突。
  • 在启动时,df.app.orchestration()df.app.entity()df.app.activity()调用被执行(例如,在主入口点导入的文件中)。

其他问题

对于其他问题,在 azure-functions-durable-js GitHub 存储库中提交 bug 报告

后续步骤