Azure / azure-functions-durable-extension

Durable Task Framework extension for Azure Functions
MIT License
716 stars 271 forks source link

Support triggering activity functions outside of an orchestration #160

Open cgillum opened 6 years ago

cgillum commented 6 years ago

Scenario

It may be desirable to trigger an activity function outside of any orchestration context (TODO: Need to enumerate what the specific use-cases would be).

Example

public static async Task QueueTrigger(
    [QueueTrigger("my-queue")] string input,
    [OrchestrationClient] DurableOrchestrationClient client)
{
    await client.CallActivityAsync("MyActivityFunction", input);
}

Design Considerations

SimonLuckenuik commented 6 years ago

Curious to hear the use cases. That sounds like stretching the Activity concept for developers unable to organize their code properly. Personally, I don't like that direction, it seems to enable bad programming practices.

Isn't that a case where you would prefer to refactor the code inside the Activity to call shared code/classes instead of assuming that the Activity is the code that needs to be shared? Example, create a Command with a command pattern, execute the command either from an Activity or from any other Function.

This is the kind of scenario where developers would get double billed without realizing it, which is bad as well.

SashaPshenychniy commented 5 years ago

@SimonLuckenuik , I have exactly the described need and my way of thinking is the following: Instead of Durable Function, I could create a bunch of Queue-trigger functions and call whichever of them I need at any point of application. Would it be bad design? I don't think so - it's just a bunch of "nano-services"... Durable Functions is just a way to reorganize such architecture in a readable way isn't it? But it removes ability to call sub-orchestrations and activities directly which is sad. I understand some complexities with returning result (in order for client not to wait blocked, you would need to either receive custom queue name where to put response to, or use ServiceBus instead). But it would be really nice to support at least fire-and-forget scenarios which are easy to support as @cgillum said.

Regarding double-payment - not sure I understand the deal, but isn't it the same with HttpTrigger - client is waiting blocked while action is executing, so if your client is another Azure Function, you'll still get billed twice. Here in fact I agree it's bad design to block synchronous logic flow while messaging part of architecture is executing (but still as intermediate solution for architecture refactoring it's viable).

By the way, if I create a Durable Function retranslator for each of my reusable activities, won't I get billed twice as well? Moreover it will be a significant performance hit if I return large object (extra serialization-deserialization by durable function), and even if I don't return anything - it's extra network hop. Idea with reusing business logic by separate functions for ActivityTrigger and QueueTrigger is what I was also planning to do in fact, but it's also not flawless: you cannot consistently control activity parallelism anymore (which I also desparately hoping to be released as announced).

As for my scenario - it's fairly simple: CQRS application where Azure Functions are used to synchronize write- and read-models. A bit simplified, but the idea is if customer data change - we rebuild corresponding customer document. If some setting on the shop-level change, we may need to update all shop-customers. There are also global settings which may affect all shops in the system. I hope it's obvious why do we want to have ability to trigger any scope of refresh directly and be able to limit number of parallel activities being executed.

joecoolish commented 4 years ago

I have a use case where I would like to call an Activity Function outside of the orchestrator context. I have a bunch of helper functions (JavaScript) that I use in my Logic Apps. Things like Merge 2 JSON outputs (why that isn't built in is beyond me) and some other data manipulation things (slice strings into 5000 char chunks for Cognitive Service calls). I would like to continue using those helper functions in my Logic Apps as well as use them in my Durable Functions without having to duplicate the code.

Is this a good use case or is there a better solution for this?

SimonLuckenuik commented 4 years ago

@joecoolish, you could simply create a package with JS helpers functions and share it between the two apps?

joecoolish commented 4 years ago

@SimonLuckenuik that's what I ended up doing. Now those utility functions are just HTTP wrappers around the functionality in the package. That's probably a better pattern anyways.