|
| 1 | +--- |
| 2 | +title: Upgrade your Durable Functions app to version 4 of the Node.js programming model |
| 3 | +description: This article shows you how to upgrade your existing Durable Functions apps running on v3 of the Node.js programming model to v4. |
| 4 | +author: hossam-nasr |
| 5 | +ms.service: azure-functions |
| 6 | +ms.date: 04/06/2023 |
| 7 | +ms.devlang: javascript, typescript |
| 8 | +ms.author: azfuncdf |
| 9 | +ms.topic: how-to |
| 10 | +zone_pivot_groups: programming-languages-set-functions-nodejs |
| 11 | +--- |
| 12 | + |
| 13 | +# Upgrade your Durable Functions app to version 4 of the Node.js programming model |
| 14 | + |
| 15 | +>[!NOTE] |
| 16 | +> Version 4 of the Node.js programming model is currently in public preview. |
| 17 | +
|
| 18 | +This article provides a guide to upgrade your existing Durable Functions app to newly-released version 4 of the Node.js programming model for Azure Functions from the existing version 3. If you are instead interested in creating a brand new v4 app, follow the Visual Studio Code quickstarts for [JavaScript](./quickstart-js-vscode.md?pivots=nodejs-model-v4) and [TypeScript](./quickstart-ts-vscode.md?pivots=nodejs-model-v4). This article uses "TIP" sections to highlight the most important concrete actions you should take to upgrade your app. Before following this guide, make sure you follow the general [version 4 upgrade guide](../functions-node-upgrade-v4.md). You can also learn more about the new v4 programming model through the [Node.js developer reference](../functions-reference-node.md?pivots=nodejs-model-v4). |
| 19 | + |
| 20 | +>[!TIP] |
| 21 | +> Before following this guide, make sure you follow the general [version 4 upgrade guide](../functions-node-upgrade-v4.md). |
| 22 | +
|
| 23 | +## Prerequisites |
| 24 | + |
| 25 | +Before following this guide, make sure you follow these steps first: |
| 26 | + |
| 27 | +- Install have [Node.js](https://nodejs.org/en/download/releases) version 18.x+. |
| 28 | +- Install [TypeScript](https://www.typescriptlang.org/) version 4.x+. |
| 29 | +- Run your app on [Azure Functions Runtime](https://learn.microsoft.com/azure/azure-functions/functions-versions?tabs=v4&pivots=programming-language-javascript) version 4.16.5+. |
| 30 | +- Install [Azure Functions Core Tools](https://learn.microsoft.com/azure/azure-functions/functions-run-local?tabs=v4) version 4.0.5095+. |
| 31 | +- Follow the general [Azure Functions Node.js programming model v4 upgrade guide](../functions-node-upgrade-v4.md). |
| 32 | + |
| 33 | +## Upgrade durable-functions npm package |
| 34 | + |
| 35 | +The v4 programming model is supported by the v3.x of the `durable-functions` npm package. In v3, you likely had `durable-functions` v2.x listed in your dependencies. Make sure to update to the (currently in preview) v3.x of the package. |
| 36 | + |
| 37 | +>[!TIP] |
| 38 | +> Upgrade to the preview v3.x of the `durable-functions` npm package. You can do this with the following command: |
| 39 | +> |
| 40 | +> ```bash |
| 41 | +> npm install durable-functions@preview |
| 42 | +> ``` |
| 43 | +
|
| 44 | +## Register your durable functions in code |
| 45 | +
|
| 46 | +In the v4 programming model, `function.json` files are a thing of the past! In v3, you would have to register your orchestration, entity, and activity triggers in a `function.json` file, and export your function implementation using `orchestrator()` or `entity()` APIs from the `durable-functions` package. With v3.x of `durable-functions`, APIs were added to the `app` namespace on the root of the package to allow you to register your durable orchestrations, entities, and activities directly in code! Find the below code snippets for examples. |
| 47 | +
|
| 48 | +#### Migrating an orchestration |
| 49 | +
|
| 50 | +:::zone pivot="programming-language-javascript" |
| 51 | +# [v4 model](#tab/v4) |
| 52 | +
|
| 53 | +```JS |
| 54 | +const df = require('durable-functions'); |
| 55 | +
|
| 56 | +const activityName = 'helloActivity'; |
| 57 | +
|
| 58 | +df.app.orchestration('durableOrchestrator', function* (context) { |
| 59 | + const outputs = []; |
| 60 | + outputs.push(yield context.df.callActivity(activityName, 'Tokyo')); |
| 61 | + outputs.push(yield context.df.callActivity(activityName, 'Seattle')); |
| 62 | + outputs.push(yield context.df.callActivity(activityName, 'Cairo')); |
| 63 | +
|
| 64 | + return outputs; |
| 65 | +}); |
| 66 | +``` |
| 67 | +
|
| 68 | +# [v3 model](#tab/v3) |
| 69 | +
|
| 70 | +```JS |
| 71 | +const df = require("durable-functions"); |
| 72 | +
|
| 73 | +const activityName = "hello" |
| 74 | +
|
| 75 | +module.exports = df.orchestrator(function* (context) { |
| 76 | + const outputs = []; |
| 77 | + outputs.push(yield context.df.callActivity(activityName, "Tokyo")); |
| 78 | + outputs.push(yield context.df.callActivity(activityName, "Seattle")); |
| 79 | + outputs.push(yield context.df.callActivity(activityName, "London")); |
| 80 | +
|
| 81 | + return outputs; |
| 82 | +}); |
| 83 | +``` |
| 84 | +
|
| 85 | +```json |
| 86 | +{ |
| 87 | + "bindings": [ |
| 88 | + { |
| 89 | + "name": "context", |
| 90 | + "type": "orchestrationTrigger", |
| 91 | + "direction": "in" |
| 92 | + } |
| 93 | + ] |
| 94 | +} |
| 95 | +``` |
| 96 | +
|
| 97 | +--- |
| 98 | +:::zone-end |
| 99 | +
|
| 100 | +:::zone pivot="programming-language-typescript" |
| 101 | +# [v4 model](#tab/v4) |
| 102 | +
|
| 103 | +```typescript |
| 104 | +import * as df from 'durable-functions'; |
| 105 | +import { OrchestrationContext, OrchestrationHandler } from 'durable-functions'; |
| 106 | +
|
| 107 | +const activityName = 'hello'; |
| 108 | +
|
| 109 | +const durableHello1Orchestrator: OrchestrationHandler = function* (context: OrchestrationContext) { |
| 110 | + const outputs = []; |
| 111 | + outputs.push(yield context.df.callActivity(activityName, 'Tokyo')); |
| 112 | + outputs.push(yield context.df.callActivity(activityName, 'Seattle')); |
| 113 | + outputs.push(yield context.df.callActivity(activityName, 'Cairo')); |
| 114 | +
|
| 115 | + return outputs; |
| 116 | +}; |
| 117 | +df.app.orchestration('durableOrchestrator', durableHello1Orchestrator); |
| 118 | +``` |
| 119 | +
|
| 120 | +# [v3 model](#tab/v3) |
| 121 | +
|
| 122 | +```typescript |
| 123 | +import * as df from "durable-functions" |
| 124 | +
|
| 125 | +const activityName = "hello" |
| 126 | +
|
| 127 | +const orchestrator = df.orchestrator(function* (context) { |
| 128 | + const outputs = []; |
| 129 | + outputs.push(yield context.df.callActivity(activityName, "Tokyo")); |
| 130 | + outputs.push(yield context.df.callActivity(activityName, "Seattle")); |
| 131 | + outputs.push(yield context.df.callActivity(activityName, "London")); |
| 132 | +
|
| 133 | + return outputs; |
| 134 | +}); |
| 135 | +
|
| 136 | +export default orchestrator; |
| 137 | +``` |
| 138 | +
|
| 139 | +```json |
| 140 | +{ |
| 141 | + "bindings": [ |
| 142 | + { |
| 143 | + "name": "context", |
| 144 | + "type": "orchestrationTrigger", |
| 145 | + "direction": "in" |
| 146 | + } |
| 147 | + ], |
| 148 | + "scriptFile": "../dist/durableOrchestrator/index.js" |
| 149 | +} |
| 150 | +``` |
| 151 | +
|
| 152 | +--- |
| 153 | +:::zone-end |
| 154 | +
|
| 155 | +
|
| 156 | +#### Migrating an entity |
| 157 | +
|
| 158 | +:::zone pivot="programming-model-javascript" |
| 159 | +
|
| 160 | +# [v4 model](#tab/v4) |
| 161 | +
|
| 162 | +```javascript |
| 163 | +const df = require('durable-functions'); |
| 164 | +
|
| 165 | +df.app.entity('Counter', (context) => { |
| 166 | + const currentValue = context.df.getState(() => 0); |
| 167 | + switch (context.df.operationName) { |
| 168 | + case 'add': |
| 169 | + const amount = context.df.getInput(); |
| 170 | + context.df.setState(currentValue + amount); |
| 171 | + break; |
| 172 | + case 'reset': |
| 173 | + context.df.setState(0); |
| 174 | + break; |
| 175 | + case 'get': |
| 176 | + context.df.return(currentValue); |
| 177 | + break; |
| 178 | + } |
| 179 | +}); |
| 180 | +``` |
| 181 | +
|
| 182 | +# [v3 model](#tab/v3) |
| 183 | +
|
| 184 | +```javascript |
| 185 | +const df = require("durable-functions"); |
| 186 | +
|
| 187 | +module.exports = df.entity(function (context) { |
| 188 | + const currentValue = context.df.getState(() => 0); |
| 189 | + switch (context.df.operationName) { |
| 190 | + case "add": |
| 191 | + const amount = context.df.getInput(); |
| 192 | + context.df.setState(currentValue + amount); |
| 193 | + break; |
| 194 | + case "reset": |
| 195 | + context.df.setState(0); |
| 196 | + break; |
| 197 | + case "get": |
| 198 | + context.df.return(currentValue); |
| 199 | + break; |
| 200 | + } |
| 201 | +}); |
| 202 | +``` |
| 203 | +
|
| 204 | +```json |
| 205 | +{ |
| 206 | + "bindings": [ |
| 207 | + { |
| 208 | + "name": "context", |
| 209 | + "type": "entityTrigger", |
| 210 | + "direction": "in" |
| 211 | + } |
| 212 | + ] |
| 213 | +} |
| 214 | +``` |
| 215 | +
|
| 216 | +--- |
| 217 | +:::zone-end |
| 218 | +
|
| 219 | +:::zone pivot="programming-model-typescript" |
| 220 | +
|
| 221 | +# [v4 model](#tab/v4) |
| 222 | +
|
| 223 | +```typescript |
| 224 | +import * as df from 'durable-functions'; |
| 225 | +import { EntityContext, EntityHandler } from 'durable-functions'; |
| 226 | +
|
| 227 | +const counterEntity: EntityHandler<number> = (context: EntityContext<number>) => { |
| 228 | + const currentValue: number = context.df.getState(() => 0); |
| 229 | + switch (context.df.operationName) { |
| 230 | + case 'add': |
| 231 | + const amount: number = context.df.getInput(); |
| 232 | + context.df.setState(currentValue + amount); |
| 233 | + break; |
| 234 | + case 'reset': |
| 235 | + context.df.setState(0); |
| 236 | + break; |
| 237 | + case 'get': |
| 238 | + context.df.return(currentValue); |
| 239 | + break; |
| 240 | + } |
| 241 | +}; |
| 242 | +df.app.entity('Counter', counterEntity); |
| 243 | +``` |
| 244 | +
|
| 245 | +# [v3 model](#tab/v3) |
| 246 | +
|
| 247 | +```typescript |
| 248 | +import * as df from "durable-functions" |
| 249 | +
|
| 250 | +const entity = df.entity(function (context) { |
| 251 | + const currentValue = context.df.getState(() => 0) as number; |
| 252 | + switch (context.df.operationName) { |
| 253 | + case "add": |
| 254 | + const amount = context.df.getInput() as number; |
| 255 | + context.df.setState(currentValue + amount); |
| 256 | + break; |
| 257 | + case "reset": |
| 258 | + context.df.setState(0); |
| 259 | + break; |
| 260 | + case "get": |
| 261 | + context.df.return(currentValue); |
| 262 | + break; |
| 263 | + } |
| 264 | +}); |
| 265 | +
|
| 266 | +export default entity; |
| 267 | +``` |
| 268 | +
|
| 269 | +```json |
| 270 | +{ |
| 271 | + "bindings": [ |
| 272 | + { |
| 273 | + "name": "context", |
| 274 | + "type": "entityTrigger", |
| 275 | + "direction": "in" |
| 276 | + } |
| 277 | + ], |
| 278 | + "scriptFile": "../dist/Counter/index.js" |
| 279 | +} |
| 280 | +``` |
| 281 | +
|
| 282 | +---- |
| 283 | +:::zone-end |
| 284 | +
|
| 285 | +#### Migrating an activity |
| 286 | +
|
| 287 | +:::zone pivot="programming-language-javascript" |
| 288 | +
|
| 289 | +# [v4 model](#tab/v4) |
| 290 | +
|
| 291 | +```javascript |
| 292 | +const df = require('durable-functions'); |
| 293 | +
|
| 294 | +df.app.activity('hello', { |
| 295 | + handler: (input) => { |
| 296 | + return `Hello, ${input}`; |
| 297 | + }, |
| 298 | +}); |
| 299 | +``` |
| 300 | +
|
| 301 | +# [v3 model](#tab/v3) |
| 302 | +
|
| 303 | +```javascript |
| 304 | +module.exports = async function (context) { |
| 305 | + return `Hello, ${context.bindings.name}!`; |
| 306 | +}; |
| 307 | +``` |
| 308 | +
|
| 309 | +```json |
| 310 | +{ |
| 311 | + "bindings": [ |
| 312 | + { |
| 313 | + "name": "name", |
| 314 | + "type": "activityTrigger", |
| 315 | + "direction": "in" |
| 316 | + } |
| 317 | + ] |
| 318 | +} |
| 319 | +``` |
| 320 | +
|
| 321 | +--- |
| 322 | +:::zone-end |
| 323 | +
|
| 324 | +:::zone pivot="programming-language-typescript" |
| 325 | +# [v4 model](#tab/v4) |
| 326 | +
|
| 327 | +```typescript |
| 328 | +import * as df from 'durable-functions'; |
| 329 | +import { ActivityHandler } from "durable-functions"; |
| 330 | +
|
| 331 | +const helloActivity: ActivityHandler = (input: string): string => { |
| 332 | + return `Hello, ${input}`; |
| 333 | +}; |
| 334 | +
|
| 335 | +df.app.activity('hello', { handler: helloActivity }); |
| 336 | +``` |
| 337 | +
|
| 338 | +# [v3 model](#tab/v3) |
| 339 | +
|
| 340 | +```typescript |
| 341 | +import { AzureFunction, Context } from "@azure/functions" |
| 342 | +
|
| 343 | +const helloActivity: AzureFunction = async function (context: Context): Promise<string> { |
| 344 | + return `Hello, ${context.bindings.name}!`; |
| 345 | +}; |
| 346 | +
|
| 347 | +export default helloActivity; |
| 348 | +``` |
| 349 | +
|
| 350 | +```json |
| 351 | +{ |
| 352 | + "bindings": [ |
| 353 | + { |
| 354 | + "name": "name", |
| 355 | + "type": "activityTrigger", |
| 356 | + "direction": "in" |
| 357 | + } |
| 358 | + ], |
| 359 | + "scriptFile": "../dist/hello/index.js" |
| 360 | +} |
| 361 | +``` |
| 362 | +
|
| 363 | +--- |
| 364 | +:::zone-end |
0 commit comments