Skip to content

Custom middleware for function triggers #595

Closed
@cgillum

Description

@cgillum

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();
}

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions