Azure / azure-functions-durable-extension

Durable Task Framework extension for Azure Functions
MIT License
715 stars 270 forks source link

Allow RaiseEventAsync to create an orchestration instance if one doesn't already exist #21

Open mikhailshilkov opened 7 years ago

mikhailshilkov commented 7 years ago

I have the following scenario (simplified):

I want 1 instance of Function B per metric type. My question is - how does the first function know/manage the instance ID and instance lifecycle. Should it instantiate new instances of Function B? I did something like this for now in Function A:

var status = await starter.GetStatusAsync(resource);
if (status == null)
{
   await starter.StartNewAsync(nameof(FunctionB), resource, new FunctionBState());
}
await starter.RaiseEventAsync(resource, "new-value", metric);

Is that a proper way? Should it also check if that status is not faulty, and restart otherwise? Isn't it a waste to GetStatus every time? Is it OK that I assigned instance ID myself or do I need to store auto-generated one somewhere?

Missing some guidance on that part of function lifecycle management.

cgillum commented 7 years ago

One part that's missing from the "actor" capability is the ability to have one instance create other instances and send messages to them. I need to open an issue to make this more clear, but we're working on a design for this pattern.

Your example above looks like the right way to go. There are some known timing issues, however, so you may want to add an await starter.CreateTimer(starter.CurrentUtcDateTime.AddSeconds(5), CancellationToken.None) immediately after your StartNewAsync call to avoid the timing issues. Let me know if this ends up working for you.

mikhailshilkov commented 7 years ago

Thanks for the answer.

My starter is actually DurableOrchestrationClient, so it doesn't seem to have timer API (as opposed to DurableOrchestrationContext). I ended up skipping the send part in the first iteration of Function A when StartNewAsync is called, so the first RaiseEventAsync call is on the second timer occurrence (if else block).

Ideally, for my kind of scenario I would expect something like RaiseEventAsync(functionName, instanceId, eventName, data) overload, and the runtime would make sure to create/restart an actor if it doesn't exist yet. A bit ServiceFabric-style virtual actors.

Another use case I was thinking of was stateful stream processing. So, I got an event hub, and each event belongs to an aggregate ID. It could be natural, if I had an eventhub-triggered "normal" function, then sending event to an actor for specific aggregate ID, who would carry state in it. Calling GetStatusAsync from eventhub-function seems wasteful and maybe error-prone (e.g. in case of actor fault). So to avoid that, it would have to keep initialization status per instance ID, which makes the function itself stateful...

Or is there another (not actor based) pattern for such stream processing scenario?

cgillum commented 7 years ago

This is great feedback, thanks!

Yes, sorry about my suggestion about CreateTimer - as you mentioned that's not available. Instead you could simply use Task.Delay.

Your stream processing scenario is a good one and will require a bit more thought on my part. I agree that keeping track of which instances have been created is possible (even using a simple, static in-memory cache) but not ideal. Regular Azure Functions does not support stateful stream processing so there aren't any good alternatives at this point (in the realm of Functions, anyways - so folks look to Service Fabric to help with this).

FYI, I'm tracking improving support for actors here: https://github.com/Azure/azure-functions-durable-extension/issues/22. I'll add some notes about stateful stream processing so we can make sure we enable that scenario.

cgillum commented 6 years ago

Updating the title and changing the labels. I think this is closely related to the Aggregator Pattern, which we plan to document and provide samples for: https://github.com/Azure/azure-functions-durable-extension/issues/166.

But I think allowing RaiseEventAsync to create the instance if it doesn't already exist would be a great feature for supporting actor-like patterns (including aggregation) more naturally.

SimonLuckenuik commented 6 years ago

I have the exact same scenario in place, currently I am forced to calls StartNew in advance to make sure everything is ready prior starting to receive events. When doing the StartNew and raising the event just after, I sometime get an error that the StartNew is not active yet (GetStatus would be returning null).

cgillum commented 6 years ago

BTW, we've fixed things so that StartNew/GetStatus won't return null anymore. You'll see that in tomorrow's update release.

ConnorMcMahon commented 4 years ago

Is this pattern required any more now that we support entities?

cgillum commented 2 years ago

Yes, the current workaround for this is to use Durable Entities: https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-entities.

Leaving this open as a reminder that we'd still like to try getting this for orchestrations.