Migrer votre application Durable Functions vers la version 4 du modèle de programmation Node.js

Cet article vous guide tout au long de la mise à niveau de votre application Durable Functions existante vers la version 4 du modèle de programmation Node.js. Cet article utilise des bannières « TIP » pour résumer les étapes clés nécessaires à la mise à niveau de votre application.

Si vous souhaitez créer une nouvelle application v4 à la place, vous pouvez suivre les guides de démarrage rapide Visual Studio Code pour JavaScript et TypeScript.

Conseil / Astuce

Avant de suivre ce guide, veillez à suivre le guide général de mise à niveau version 4.

Prerequisites

Avant de suivre ce guide, procédez comme suit :

Mettre à niveau le durable-functions package npm

Note

La version du modèle de programmation ne doit pas être confondue avec la version du durable-functions package. durable-functions la version 3.x du package est requise pour le modèle de programmation v4, tandis que durable-functions la version 2.x est requise pour le modèle de programmation v3.

Le modèle de programmation v4 est pris en charge par la version v3.x du durable-functions package npm. Dans votre application de modèle de programmation v3, vous avez probablement répertorié durable-functions v2.x dans vos dépendances. Veillez à effectuer une mise à jour vers la version v3.x du durable-functions package.

Conseil / Astuce

Effectuez une mise à niveau vers v3.x du durable-functions package npm avec la commande suivante :

npm install durable-functions

Enregistrer vos déclencheurs Durable Functions

Dans le modèle de programmation v4, vous ne déclarez plus de déclencheurs et de liaisons dans un fichier distinct function.json . Au lieu de cela, vous pouvez inscrire vos déclencheurs et liaisons Durable Functions directement dans le code, à l’aide des nouvelles API trouvées dans l’espace app de noms à la racine du durable-functions package. Les extraits de code suivants montrent des exemples.

Migration d’une orchestration

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

Migration d’une entité

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

Migration d’une activité

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

Conseil / Astuce

Supprimez les function.json fichiers de votre application Durable Functions. Au lieu de cela, enregistrez vos fonctions durables à l'aide des méthodes de l'espace de noms app : df.app.orchestration(), df.app.entity() et df.app.activity().

Inscrire votre liaison d'entrée de client durable

Dans le modèle v4, l’enregistrement des liaisons d’entrée secondaires, telles que les clients pérennes, est également réalisé dans le code. Utilisez la méthode input.durableClient() pour enregistrer une liaison d’entrée client durable à une fonction de votre choix. Dans le corps de la fonction, utilisez getClient() pour récupérer l’instance du client, comme précédemment. L’exemple suivant utilise une fonction déclenchée par 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,
});

Conseil / Astuce

Utilisez la input.durableClient() méthode pour inscrire une entrée supplémentaire client durable dans votre fonction cliente. Utilisez getClient() normalement pour récupérer une DurableClient instance.

Mettre à jour vos appels d’API Client Durable

Plusieurs API sur la classe DurableClient (renommée de DurableOrchestrationClient) ont été simplifiées dans v3.x de durable-functions pour faciliter leur utilisation. Pour de nombreux arguments facultatifs aux API, vous passez maintenant un objet options, au lieu de plusieurs arguments facultatifs discrets. L’exemple suivant montre ces modifications :

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

Le tableau suivant répertorie toutes les modifications :

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

Conseil / Astuce

Veillez à mettre à jour vos DurableClient appels d’API à partir d’arguments facultatifs discrets vers des objets options, le cas échéant. Consultez la liste précédente pour toutes les API affectées.

Mettre à jour les appels vers l'API callHttp

Dans la version v3.x de durable-functions, l’API callHttp() pour DurableOrchestrationContext a été mise à jour. Les modifications suivantes ont été apportées :

  • Acceptez un objet options pour tous les arguments, au lieu de plusieurs arguments facultatifs, pour être plus similaire aux frameworks tels qu’Express.
  • Renommer uri l’argument en url
  • Renommer content l’argument en body
  • Rendre asynchronousPatternEnabled obsolète au profit de enablePolling.

Si vos orchestrations ont utilisé l’API callHttp , veillez à mettre à jour les appels d’API pour qu’ils soient conformes aux modifications précédentes. L’exemple suivant montre la syntaxe mise à jour :

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

Conseil / Astuce

Pour utiliser le nouvel objet d'options, mettez à jour vos appels d’API à callHttp dans vos orchestrations.

Utiliser de nouveaux types

Le durable-functions package expose désormais de nouveaux types qui n’ont pas été exportés précédemment. Ces types vous permettent de renforcer la typage de vos fonctions et de fournir une meilleure sécurité de typage pour vos orchestrations, entités et activités. Ils améliorent également IntelliSense pour la création de ces fonctions.

La liste suivante inclut certains des nouveaux types exportés :

  • OrchestrationHandleret OrchestrationContext pour les orchestrations
  • EntityHandler et EntityContext pour les entités
  • ActivityHandler pour les activités
  • DurableClient classe pour les fonctions clientes

Conseil / Astuce

Tapez fortement vos fonctions à l’aide de nouveaux types exportés à partir du durable-functions package.

Résolution des problèmes

Si vous voyez l’erreur suivante lors de l’exécution de votre code d’orchestration, vérifiez que vous utilisez au moins v4.25 du runtime Azure Functions ou au moins v4.0.5382 des Azure Functions Core Tools si vous effectuez une exécution locale.

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

Si cela ne fonctionne pas ou si vous rencontrez d’autres problèmes, vous pouvez toujours déposer un rapport de bogue dans notre dépôt GitHub.