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