将 Durable Functions 应用迁移到 Node.js 编程模型版本 4
本文内容
先决条件
升级 durable-functions npm 包
注册 Durable Functions 触发器
注册 Durable 客户端输入绑定
更新持久客户端 API 调用
更新对 callHttp API 的调用
利用新类型
疑难解答
显示另外 4 个
本文提供了将现有 Durable Functions 应用升级到 Node.js 编程模型版本 4 的指南。 请注意,本文使用“TIP”横幅来总结升级应用所需的关键步骤。
如果你对创建全新的 v4 应用感兴趣,则可以遵循适用于 JavaScript 和 TypeScript 的 Visual Studio Code 快速入门。
在遵循本指南之前,请确保先执行以下步骤:
升级 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
在 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;
});
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"
}
迁移活动
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"
}
提示
从 Durable Functions 应用中删除 function.json
文件。 而是使用 app
命名空间上的方法注册 Durable Functions:df.app.orchestration()
、df.app.entity()
和 df.app.activity()
。
在 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
},
});
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"
}
提示
使用 input.durableClient()
方法将持久客户端额外输入注册到客户端函数。 照常使用 getClient()
检索 DurableClient
实例。
在 durable-functions
的 v3.x
中,DurableClient
类(从 DurableOrchestrationClient
重命名)上的多个 API 已被简化,以使调用它们更容易、更精简。 对于 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);
在下面,找到完整的更改列表:
展开表
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,请参阅上面的列表。
在 durable-functions
的 v3.x 中,更新了 DurableOrchestrationContext
的 callHttp()
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 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
);
提示
更新业务流程中对 callHttp
的 API 调用,以使用新的选项对象。
durable-functions
包现在会公开以前未导出的新类型! 这使你可以更强地类型化函数,并为业务流程、实体和活动提供更强的类型安全性! 这也改进了用于创作这些函数的 IntelliSense。
下面是一些新的导出类型:
OrchestrationHandler
和 OrchestrationContext
(用于业务流程)
EntityHandler
和 EntityContext
(用于实体)
ActivityHandler
(用于活动)
DurableClient
类(用于客户端函数)
提示
利用从 durable-functions
包中导出的新类型来强类型化你的函数!
如果在运行业务流程代码时看到以下错误,请确保至少在 Azure Functions 运行时 的 v4.25
上运行,如果在本地运行,请确保至少在 Azure Functions Core Tools 的 v4.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 报告。