Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
bc26cc7
add durable prototype
hossam-nasr Sep 27, 2022
fb8adb2
local tests
hossam-nasr Sep 27, 2022
995c3a5
add durable orchestration and counter
hossam-nasr Nov 3, 2022
76cf4de
format
hossam-nasr Nov 3, 2022
bbd5f13
clean up
hossam-nasr Nov 3, 2022
67aa6bf
more clean up
hossam-nasr Nov 3, 2022
7908129
add rimraf
hossam-nasr Nov 3, 2022
5f11b15
switch back main
hossam-nasr Nov 3, 2022
5e988d3
remove prettier config file
hossam-nasr Nov 4, 2022
2f6db2a
separate function definitions
hossam-nasr Nov 4, 2022
501ac69
structure
hossam-nasr Nov 4, 2022
12405cc
name => input
hossam-nasr Nov 4, 2022
f038144
req -> request
hossam-nasr Nov 4, 2022
948b5ff
body -> text()
hossam-nasr Nov 4, 2022
8b400f5
add get
hossam-nasr Nov 4, 2022
7ca8192
naming
hossam-nasr Nov 4, 2022
418414e
organization
hossam-nasr Nov 4, 2022
b9c1f5f
fix route
hossam-nasr Nov 4, 2022
2438a1f
hello naming
hossam-nasr Nov 4, 2022
2bc476a
update types
hossam-nasr Nov 5, 2022
2f8c730
merge main
hossam-nasr Nov 5, 2022
af966ae
revert rimraf
hossam-nasr Nov 5, 2022
4ef5943
add complex example
hossam-nasr Nov 5, 2022
df691cb
update APIs
hossam-nasr Nov 9, 2022
d51d5f1
remove df.client()
hossam-nasr Dec 9, 2022
63ce7bb
rename to httpStart
hossam-nasr Dec 16, 2022
8040c03
rename to durableClient
hossam-nasr Dec 16, 2022
d5a55e3
add app namespace
hossam-nasr Dec 16, 2022
ba56c7b
remove generics from activity
hossam-nasr Dec 16, 2022
a5f1913
update package version
hossam-nasr Dec 16, 2022
695b94b
merge main
hossam-nasr Dec 16, 2022
86c56b5
merge conflicts
hossam-nasr Dec 16, 2022
bcdcb8a
use the new HttpResponse class
hossam-nasr Dec 16, 2022
cf0dc35
rename entityHandler
hossam-nasr Dec 16, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 104 additions & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@
"test": "mocha 'dist/test/**/*.test.js'"
},
"dependencies": {
"@azure/functions": "^4.0.0-alpha.5"
"@azure/functions": "^4.0.0-alpha.5",
"durable-functions": "file:../../../durable/js-sdk"
},
"devDependencies": {
"@types/chai": "^4.0.0",
"@types/mocha": "^9.0.0",
"@types/node": "^18.0.0",
"chai": "^4.0.0",
"func-cli-nodejs-v4": "4.0.4764",
"mocha": "^9.0.0",
"typescript": "^4.0.0",
"func-cli-nodejs-v4": "4.0.4764"
"typescript": "^4.0.0"
},
"main": "dist/src/index.js"
"main": "dist/src/functions/*.js"
}
40 changes: 40 additions & 0 deletions src/functions/durableEntity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { HttpRequest, InvocationContext, output, trigger } from '@azure/functions';
import * as df from 'durable-functions';
import { DurableClientHandler, EntityHandler } from 'durable-functions';

// Replace with your own Durable entity name
const entityName = 'Counter';

const clientHandler: DurableClientHandler = async (_context: InvocationContext, req: HttpRequest, client) => {
const id: string = req.params.id;
const entityId = new df.EntityId(entityName, id);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized: there's only 1 type of DF input bindings: the durableClient.

With that in mind, do we really need this second parameter? Can we not just call df.getClient(context) and have the method always retrieve the durableClient within?

Copy link
Collaborator Author

@hossam-nasr hossam-nasr Dec 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With that in mind, do we really need this second parameter?

Sadly, yes. Under the hood, df.getClient() calls context.getInput(), which expects an input binding as an argument (clientInput in this case), so the functions needs to also pass that argument to the SDK. In the old model, this wasn't as necessary because all the bindings would live inside the context, so the SDK could just loop over all of them until it finds the client input binding, but that is no longer the case in the new model

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be another advantage of something like df.client() that I had before, because the SDK would be managing the durable client input binding on behalf of the user and the user doesn't have to worry about it or pass it to the SDK or even know it exists

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. At this time, this is my biggest pain point with the new programming model. Moving forward, I would like for us to prioritize removing this boilerplate, as it is rather clunky and DF apps always need a client.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this 100%. But I think we agreed that that shouldn't be in this first iteration. Let me create an issue for this on the Durable SDK to track this so we don't block this PR.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the old model, this wasn't as necessary because all the bindings would live inside the context, so the SDK could just loop over all of them until it finds the client input binding, but that is no longer the case in the new model

Ftr, the new model could do the same thing where it loops over the context to find the durable client. The library package might not allow you to loop over bindings right now, but we can always change it. That may be helpful outside of durable as well, which you should always keep an eye out for.

If we can come up with a fancy new design to improve on the client experience, that's great - but in the interest of time we should keep the "match the old model" option on the table.

if (req.method === 'POST') {
// increment value
await client.signalEntity(entityId, 'add', 1);
} else {
// read current state of entity
const stateResponse = await client.readEntityState(entityId);
return {
body: stateResponse.entityState,
};
}
};
df.client('durableEntityStart1', trigger.http({ route: 'entity/{id}' }), output.http({}), clientHandler);

const entityHandler: EntityHandler<number> = (context) => {
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.entity(entityName, entityHandler);
41 changes: 41 additions & 0 deletions src/functions/durableOrchestration-complex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { HttpRequest, InvocationContext, output, trigger } from '@azure/functions';
import * as df from 'durable-functions';
import { ActivityHandler, DurableClientHandler, OrchestrationHandler } from 'durable-functions';

// Replace with the name of your Durable Functions Activity
const activityName = 'hello2';

const orchestrator: OrchestrationHandler = 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;
};
df.orchestration('durableOrchestrator2', orchestrator);

const helloActivity: ActivityHandler<string> = (_context: InvocationContext, input: string) => {
return `Hello, ${input}`;
};
df.activityComplex<string>(activityName, {
extraInputs: [], // could be used to add extra inputs
extraOutputs: [], // could be used to add extra outputs
handler: helloActivity,
});

const clientHandler: DurableClientHandler = async (context: InvocationContext, request: HttpRequest, client) => {
const instanceId = await client.startNew(request.params.orchestratorName, undefined, request.text());
context.log(`Started orchestration with ID = '${instanceId}'.`);
return client.createCheckStatusResponse(request, instanceId);
};

df.clientComplex('durableOrchestrationStart2', {
trigger: trigger.http({
route: 'orchestrators/{orchestratorName}',
}),
return: output.http({}),
extraInputs: [], // could be used to add extra inputs
extraOutputs: [], // could be used to add extra outputs
handler: clientHandler,
});
28 changes: 28 additions & 0 deletions src/functions/durableOrchestration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { HttpRequest, InvocationContext } from '@azure/functions';
import * as df from 'durable-functions';
import { ActivityHandler, DurableClientHandler, OrchestrationHandler } from 'durable-functions';

// Replace with the name of your Durable Functions Activity
const activityName = 'hello';

const orchestrator: OrchestrationHandler = 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;
};
df.orchestration('durableOrchestrator1', orchestrator);

const helloActivity: ActivityHandler<string> = (_context: InvocationContext, input: string) => {
return `Hello, ${input}`;
};
df.activity<string>(activityName, helloActivity);

const clientHandler: DurableClientHandler = async (context: InvocationContext, request: HttpRequest, client) => {
const instanceId = await client.startNew(request.query.get('orchestratorName'), undefined, request.text());
context.log(`Started orchestration with ID = '${instanceId}'.`);
return client.createCheckStatusResponse(request, instanceId);
};
df.httpClient('durableOrchestrationStart1', clientHandler);