Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom middleware for function triggers #595

Closed
cgillum opened this issue Jun 24, 2022 · 0 comments · Fixed by #652
Closed

Custom middleware for function triggers #595

cgillum opened this issue Jun 24, 2022 · 0 comments · Fixed by #652
Assignees

Comments

@cgillum
Copy link
Member

cgillum commented Jun 24, 2022

Summary

The Java language worker should support a custom middleware feature that enables the following:

  • Ability to inspect and change trigger input type and value
  • Ability to control when and whether to invoke the target function
  • Ability to catch exceptions that are thrown from the function execution
  • Ability to both inspect and change the trigger return value
  • Ability to automatically register middleware for certain trigger types

This feature is intended to be similar to the middleware support that exists in the .NET Isolated language worker. The primary use case is Durable Functions, but ideally this feature is useful to a wide variety of user scenarios.

Here is some example code that shows how the middleware was implemented for the .NET Isolated worker: DurableTaskFunctionsMiddleware

More context on the requirements for Durable Functions can be found here.

Background / Problem (Durable Functions)

The Durable Functions experience for Java is more convoluted than the .NET experience because developers are required to write boilerplate code in their orchestrator functions. Consider this example:

@FunctionName("Chaining")
public String helloCitiesOrchestrator(
        @DurableOrchestrationTrigger(name = "runtimeState") String runtimeState) {
    return OrchestrationRunner.loadAndRun(runtimeState, ctx -> {
        String input = ctx.getInput(String.class);
        int x = ctx.callActivity("F1", input, int.class).await();
        int y = ctx.callActivity("F2", x, int.class).await();
        int z = ctx.callActivity("F3", y, int.class).await();
        return  ctx.callActivity("F4", z, double.class).await();
    });
}

Notice the use of OrchestrationRunner.loadAndRun(...) to wrap the actual orchestration logic. Compare this to the equivalent C# implementation, which doesn't require this boilerplate:

[FunctionName("Chaining")]
public static async Task<object> Run(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
        var x = await context.CallActivityAsync<object>("F1", null);
        var y = await context.CallActivityAsync<object>("F2", x);
        var z = await context.CallActivityAsync<object>("F3", y);
        return  await context.CallActivityAsync<object>("F4", z);
}

The C# experience is clean and consistent with other trigger types whereas the Java experience is unnatural, verbose, and prone to coding errors. A properly supported middleware feature would allow a Java orchestrator function to look like this:

@FunctionName("Chaining")
public double helloCitiesOrchestrator(
        @DurableOrchestrationTrigger(name = "runtimeState") String input) {
    int x = ctx.callActivity("F1", input, int.class).await();
    int y = ctx.callActivity("F2", x, int.class).await();
    int z = ctx.callActivity("F3", y, int.class).await();
    return  ctx.callActivity("F4", z, double.class).await();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment