Compartir a través de


Migración de la aplicación Durable Functions a la versión 4 del modelo de programación de Node.js

En este artículo se proporciona una guía para actualizar la aplicación de Durable Functions existente a la versión 4 del modelo de programación de Node.js. Tenga en cuenta que en este artículo se usan banners "TIP" para resumir los pasos clave necesarios para actualizar la aplicación.

Si tiene interés en crear una nueva aplicación v4 en su lugar, puede seguir las guías de inicio rápido de Visual Studio Code para JavaScript y TypeScript.

Sugerencia

Antes de seguir esta guía, asegúrese de seguir la guía de actualización general de la versión 4.

Requisitos previos

Antes de seguir esta guía, asegúrese de seguir estos pasos primero:

Actualice el paquete npm durable-functions.

Nota

La versión del modelo de programación no debe confundirse con la versión del paquete durable-functions. La versión 3.x del paquete durable-functions es necesaria para el modelo de programación v4, mientras que la versión 2.x durable-functions es necesaria para el modelo de programación v3.

El modelo de programación v4 es compatible con la versión v3.x del paquete npm durable-functions. En la aplicación del modelo de programación v3, es probable que tuviera durable-functions v2.x en las dependencias. Asegúrese de actualizar a la versión 3.x del paquete durable-functions.

Sugerencia

Actualice a la versión v3.x del paquete npm durable-functions. Para ello, use el siguiente comando:

npm install durable-functions

Registro de los desencadenadores de Durable Functions

En el modelo de programación v4, declarar desencadenadores y enlaces en un archivo function.json independiente es algo del pasado. Ahora puede registrar los desencadenadores y enlaces de Durable Functions directamente en el código, mediante las nuevas API que se encuentran en el espacio de nombres app en la raíz del paquete durable-functions. Consulte los fragmentos de código siguientes para obtener ejemplos.

Migración de una orquestación

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

Migración de una entidad

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

Migración de una actividad

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

Sugerencia

Quite los archivos function.json de la aplicación de Durable Functions. En su lugar, registre las instancias de Durable Functions mediante los métodos en el espacio de nombres app: df.app.orchestration(), df.app.entity() y df.app.activity().

Registro del enlace de entrada de cliente de Durable

En el modelo v4, el registro de enlaces de entrada secundarios, como los clientes de Durable, también se realiza en el código. Use el método input.durableClient() para registrar un enlace de entrada de cliente de Durable en una función de su elección. En el cuerpo de la función, use getClient() para recuperar la instancia de cliente, como antes. En el ejemplo siguiente se muestra un ejemplo mediante una función desencadenada por 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,
});

Sugerencia

Use el método input.durableClient() para registrar una entrada adicional de cliente de Durable en la función de cliente. Use getClient() como de forma habitual para recuperar una instancia DurableClient.

Actualización de las llamadas API de cliente de Durable

En v3.x de durable-functions, se han simplificado varias API de la clase DurableClient (cuyo nombre ha cambiado de DurableOrchestrationClient) para facilitar su llamada y simplificarlas. Para muchos argumentos opcionales para las API, ahora se pasa un objeto options, en lugar de varios argumentos opcionales discretos. A continuación se muestra un ejemplo de estos cambios:

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

A continuación se muestra una lista completa de los cambios:

Modelo V3 (durable-functions v2.x) Modelo 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>;

Sugerencia

Asegúrese de actualizar las llamadas API de DurableClient de argumentos opcionales discretos a objetos options, si procede. Consulte la lista anterior para ver todas las API afectadas.

Actualización de llamadas a la API callHttp

En la versión 3.x de durable-functions, se actualizó la API de callHttp() para DurableOrchestrationContext. Se han realizado los siguientes cambios:

  • Acepte un objeto options para todos los argumentos, en lugar de varios argumentos opcionales, para que sea más similar a los marcos como Express.
  • Cambie el nombre del argumento uri a url.
  • Cambie el nombre del argumento content a body.
  • Ponga en desuso la marca asynchronousPatternEnabled en favor de enablePolling.

Si las orquestaciones usaban la API callHttp, asegúrese de actualizar estas llamadas API para que se ajusten a los cambios anteriores. Vea el ejemplo siguiente:

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

Sugerencia

Actualice las llamadas API a callHttp dentro de las orquestaciones para usar el nuevo objeto de opciones.

Aprovechamiento de nuevos tipos

El paquete durable-functions ahora expone nuevos tipos que no se exportaron previamente. Esto le permite escribir más fuertemente las funciones y proporcionar una mayor seguridad de tipos para las orquestaciones, entidades y actividades. Esto también mejora IntelliSense para crear estas funciones.

A continuación se muestran algunos de los nuevos tipos exportados:

  • OrchestrationHandler y OrchestrationContext para orquestaciones
  • EntityHandler y EntityContext para entidades
  • ActivityHandler para actividades
  • La clase DurableClient para funciones de cliente

Sugerencia

Escriba fuertemente las funciones aprovechando los nuevos tipos exportados desde el paquete durable-functions.

Solución de problemas

Si ve el siguiente error al ejecutar el código de orquestación, asegúrese de que se ejecuta al menos v4.25 en Azure Functions Runtime o al menos v4.0.5382 de Azure Functions Core Tools si se ejecuta localmente.

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

Si esto no funciona o si encuentra algún otro problema, siempre puede presentar un informe de errores en nuestro repositorio de GitHub.