212 lines
5.6 KiB
Markdown
212 lines
5.6 KiB
Markdown
# Durable Task Scheduler — JavaScript
|
|
|
|
## Learn More
|
|
|
|
- [Durable Task Scheduler documentation](https://learn.microsoft.com/azure/durable-task-scheduler/)
|
|
- [Durable Functions JavaScript guide](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-overview?tabs=javascript)
|
|
|
|
## Durable Functions Setup
|
|
|
|
### Required npm Packages
|
|
|
|
```json
|
|
{
|
|
"dependencies": {
|
|
"@azure/functions": "^4.0.0",
|
|
"durable-functions": "^3.0.0"
|
|
}
|
|
}
|
|
```
|
|
|
|
> **💡 Finding latest versions**: Run `npm view durable-functions version` or check [npmjs.com/package/durable-functions](https://www.npmjs.com/package/durable-functions) for the latest stable release.
|
|
|
|
### host.json
|
|
|
|
```json
|
|
{
|
|
"version": "2.0",
|
|
"extensions": {
|
|
"durableTask": {
|
|
"hubName": "default",
|
|
"storageProvider": {
|
|
"type": "durabletask-scheduler",
|
|
"connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
|
|
}
|
|
}
|
|
},
|
|
"extensionBundle": {
|
|
"id": "Microsoft.Azure.Functions.ExtensionBundle",
|
|
"version": "[4.*, 5.0.0)"
|
|
}
|
|
}
|
|
```
|
|
|
|
### local.settings.json
|
|
|
|
```json
|
|
{
|
|
"IsEncrypted": false,
|
|
"Values": {
|
|
"FUNCTIONS_WORKER_RUNTIME": "node",
|
|
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
|
|
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"
|
|
}
|
|
}
|
|
```
|
|
|
|
## Minimal Example
|
|
|
|
```javascript
|
|
const { app } = require("@azure/functions");
|
|
const df = require("durable-functions");
|
|
|
|
// Activity
|
|
df.app.activity("sayHello", {
|
|
handler: (city) => `Hello ${city}!`,
|
|
});
|
|
|
|
// Orchestrator
|
|
df.app.orchestration("myOrchestration", function* (context) {
|
|
const result1 = yield context.df.callActivity("sayHello", "Tokyo");
|
|
const result2 = yield context.df.callActivity("sayHello", "Seattle");
|
|
return `${result1}, ${result2}`;
|
|
});
|
|
|
|
// HTTP Starter
|
|
app.http("HttpStart", {
|
|
route: "orchestrators/{orchestrationName}",
|
|
methods: ["POST"],
|
|
authLevel: "function",
|
|
extraInputs: [df.input.durableClient()],
|
|
handler: async (request, context) => {
|
|
const client = df.getClient(context);
|
|
const instanceId = await client.startNew(request.params.orchestrationName);
|
|
return client.createCheckStatusResponse(request, instanceId);
|
|
},
|
|
});
|
|
```
|
|
|
|
## Workflow Patterns
|
|
|
|
### Fan-Out/Fan-In
|
|
|
|
```javascript
|
|
df.app.orchestration("fanOutFanIn", function* (context) {
|
|
const cities = ["Tokyo", "Seattle", "London", "Paris", "Berlin"];
|
|
|
|
// Fan-out: schedule all activities in parallel
|
|
const tasks = cities.map((city) => context.df.callActivity("sayHello", city));
|
|
|
|
// Fan-in: wait for all to complete
|
|
const results = yield context.df.Task.all(tasks);
|
|
return results;
|
|
});
|
|
```
|
|
|
|
### Human Interaction
|
|
|
|
```javascript
|
|
df.app.orchestration("approvalWorkflow", function* (context) {
|
|
yield context.df.callActivity("sendApprovalRequest", context.df.getInput());
|
|
|
|
// Wait for approval event with timeout
|
|
const expiration = new Date(context.df.currentUtcDateTime);
|
|
expiration.setDate(expiration.getDate() + 3);
|
|
|
|
const approvalTask = context.df.waitForExternalEvent("ApprovalEvent");
|
|
const timeoutTask = context.df.createTimer(expiration);
|
|
|
|
const winner = yield context.df.Task.any([approvalTask, timeoutTask]);
|
|
|
|
if (winner === approvalTask) {
|
|
return approvalTask.result ? "Approved" : "Rejected";
|
|
}
|
|
return "Timed out";
|
|
});
|
|
```
|
|
|
|
## Orchestration Determinism
|
|
|
|
| ❌ NEVER | ✅ ALWAYS USE |
|
|
|----------|--------------|
|
|
| `new Date()` | `context.df.currentUtcDateTime` |
|
|
| `Math.random()` | Pass random values from activities |
|
|
| `setTimeout()` | `context.df.createTimer()` |
|
|
| Direct I/O, HTTP, database | `context.df.callActivity()` |
|
|
|
|
### Replay-Safe Logging
|
|
|
|
```javascript
|
|
df.app.orchestration("myOrchestration", function* (context) {
|
|
if (!context.df.isReplaying) {
|
|
console.log("Started"); // Only logs once, not on replay
|
|
}
|
|
const result = yield context.df.callActivity("myActivity", "input");
|
|
return result;
|
|
});
|
|
```
|
|
|
|
## Error Handling & Retry
|
|
|
|
```javascript
|
|
df.app.orchestration("workflowWithRetry", function* (context) {
|
|
const retryOptions = new df.RetryOptions(5000, 3); // firstRetryInterval, maxAttempts
|
|
retryOptions.backoffCoefficient = 2.0;
|
|
retryOptions.maxRetryIntervalInMilliseconds = 60000;
|
|
|
|
try {
|
|
const result = yield context.df.callActivityWithRetry(
|
|
"unreliableService",
|
|
retryOptions,
|
|
context.df.getInput()
|
|
);
|
|
return result;
|
|
} catch (ex) {
|
|
context.df.setCustomStatus({ error: ex.message });
|
|
yield context.df.callActivity("compensationActivity", context.df.getInput());
|
|
return "Compensated";
|
|
}
|
|
});
|
|
```
|
|
|
|
## Durable Task SDK (Non-Functions)
|
|
|
|
For applications running outside Azure Functions (containers, VMs, Azure Container Apps, Azure Kubernetes Service):
|
|
|
|
```javascript
|
|
const { createAzureManagedWorkerBuilder, createAzureManagedClient } = require("@microsoft/durabletask-js-azuremanaged");
|
|
|
|
const connectionString = "Endpoint=http://localhost:8080;Authentication=None;TaskHub=default";
|
|
|
|
// Activity
|
|
const sayHello = async (_ctx, name) => `Hello ${name}!`;
|
|
|
|
// Orchestrator
|
|
const myOrchestration = async function* (ctx, name) {
|
|
const result = yield ctx.callActivity(sayHello, name);
|
|
return result;
|
|
};
|
|
|
|
async function main() {
|
|
// Worker
|
|
const worker = createAzureManagedWorkerBuilder(connectionString)
|
|
.addOrchestrator(myOrchestration)
|
|
.addActivity(sayHello)
|
|
.build();
|
|
|
|
await worker.start();
|
|
|
|
// Client
|
|
const client = createAzureManagedClient(connectionString);
|
|
const instanceId = await client.scheduleNewOrchestration("myOrchestration", "World");
|
|
const state = await client.waitForOrchestrationCompletion(instanceId, true, 30);
|
|
console.log("Output:", state.serializedOutput);
|
|
|
|
await client.stop();
|
|
await worker.stop();
|
|
}
|
|
|
|
main().catch(console.error);
|
|
```
|
|
|