本指南介绍升级到 v4 Node.js 编程模型时所需的Durable Functions特定更改。 若要改为创建新的 v4 应用,请参阅 JavaScript 和 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() |
| 活动 |
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;
});
const df = require("durable-functions");
const activityName = "hello"
module.exports = df.orchestrator(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, "London"));
return outputs;
});
{
"bindings": [
{
"name": "context",
"type": "orchestrationTrigger",
"direction": "in"
}
]
}
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);
import * as df from "durable-functions"
const activityName = "hello"
const orchestrator = df.orchestrator(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, "London"));
return outputs;
});
export default orchestrator;
{
"bindings": [
{
"name": "context",
"type": "orchestrationTrigger",
"direction": "in"
}
],
"scriptFile": "../dist/durableOrchestrator/index.js"
}
实体
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;
}
});
const df = require("durable-functions");
module.exports = df.entity(function (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;
}
});
{
"bindings": [
{
"name": "context",
"type": "entityTrigger",
"direction": "in"
}
]
}
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);
import * as df from "durable-functions"
const entity = df.entity(function (context) {
const currentValue = context.df.getState(() => 0) as number;
switch (context.df.operationName) {
case "add":
const amount = context.df.getInput() as number;
context.df.setState(currentValue + amount);
break;
case "reset":
context.df.setState(0);
break;
case "get":
context.df.return(currentValue);
break;
}
});
export default entity;
{
"bindings": [
{
"name": "context",
"type": "entityTrigger",
"direction": "in"
}
],
"scriptFile": "../dist/Counter/index.js"
}
Activity
const df = require('durable-functions');
df.app.activity('hello', {
handler: (input) => {
return `Hello, ${input}`;
},
});
module.exports = async function (context) {
return `Hello, ${context.bindings.name}!`;
};
{
"bindings": [
{
"name": "name",
"type": "activityTrigger",
"direction": "in"
}
]
}
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 });
import { AzureFunction, Context } from "@azure/functions"
const helloActivity: AzureFunction = async function (context: Context): Promise<string> {
return `Hello, ${context.bindings.name}!`;
};
export default helloActivity;
{
"bindings": [
{
"name": "name",
"type": "activityTrigger",
"direction": "in"
}
],
"scriptFile": "../dist/hello/index.js"
}
在 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
},
});
const df = require("durable-functions");
module.exports = async function (context, req) {
const client = df.getClient(context);
// Use client in function body
};
{
"bindings": [
{
"authLevel": "anonymous",
"name": "req",
"type": "httpTrigger",
"direction": "in",
"route": "orchestrators/{functionName}",
"methods": [
"post",
"get"
]
},
{
"name": "$return",
"type": "http",
"direction": "out"
},
{
"name": "starter",
"type": "durableClient",
"direction": "in"
}
]
}
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,
});
import * as df from "durable-functions"
import { AzureFunction, Context, HttpRequest } from "@azure/functions"
const durableHttpStart: AzureFunction = async function (context: Context): Promise<any> {
const client = df.getClient(context);
// Use client in function body
};
export default durableHttpStart;
{
"bindings": [
{
"authLevel": "anonymous",
"name": "req",
"type": "httpTrigger",
"direction": "in",
"route": "orchestrators/{functionName}",
"methods": [
"post",
"get"
]
},
{
"name": "$return",
"type": "http",
"direction": "out"
},
{
"name": "starter",
"type": "durableClient",
"direction": "in"
}
],
"scriptFile": "../dist/durableHttpStart/index.js"
}
更新 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 = df.getClient(context);
const status = await client.getStatus('instanceId', false, false, true);
const client: DurableClient = df.getClient(context);
const status: DurableOrchestrationStatus = await client.getStatus('instanceId', {
showHistory: false,
showHistoryOutput: false,
showInput: true
});
const client: DurableOrchestrationClient = df.getClient(context);
const status: DurableOrchestrationStatus = await client.getStatus('instanceId', false, false, 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。 |
uri → url |
已重命名以保持一致性。 |
content → body |
已重命名以保持一致性。 |
asynchronousPatternEnabled → enablePolling |
为了清楚起见,已重命名。 |
如果编排使用 callHttp,请将调用更新为新语法:
const restartResponse = yield context.df.callHttp({
method: "POST",
url: `https://example.com`,
body: "body",
enablePolling: false
});
const response = yield context.df.callHttp(
"POST",
`https://example.com`,
"body", // request content
undefined, // no request headers
undefined, // no token source
false // disable polling
);
const restartResponse = yield context.df.callHttp({
method: "POST",
url: `https://example.com`,
body: "body",
enablePolling: false
});
const response = yield context.df.callHttp(
"POST",
`https://example.com`,
"body", // request content
undefined, // no request headers
undefined, // no token source
false // disable polling
);
使用新的导出类型增强类型安全性
包 durable-functions 现在公开了以前未导出的新类型。 这些类型允许你更强地键入函数,并为业务流程、实体和活动提供更强的类型安全性。 它们还改进了用于创作这些函数的 IntelliSense。
以下列表包括一些新的导出类型:
-
OrchestrationHandler和OrchestrationContext编排
-
EntityHandler 和 EntityContext 实体
-
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 报告。
后续步骤