diff --git a/docs/src/main/asciidoc/spring-cloud-stream.adoc b/docs/src/main/asciidoc/spring-cloud-stream.adoc index f6e293e5b..02f7023a5 100644 --- a/docs/src/main/asciidoc/spring-cloud-stream.adoc +++ b/docs/src/main/asciidoc/spring-cloud-stream.adoc @@ -1485,6 +1485,80 @@ NOTE: If you need to support dynamic destinations with multiple binder types, us Also, please see <> section to see how yet another option (StreamBridge) can be utilized for similar cases. +=== Post processing (after sending message) + +Once function is invoked, its result is sent by the framework to a target destination which effectively completes function invocation cycle. + +However such cycle may not be fully complete from the business standpoint until some additional tasks are performed *after* completion of this cycle. +While this could be accomplished with a simple combination of `Consumer` and `StreamBridge` as described in this +https://stackoverflow.com/questions/75917883/post-processing-after-spring-cloud-stream-function[Stack Overflow post], since version 4.0.3 the framework +provides a more idiomatic approach to solve this issue via `PostProcessingFunction` provided by Spring Cloud Function project. +The `PostProcessingFunction` is a special semi-marker function which contains one additional method `postProcess(Message>)` designed +to provide a place for implementing such post processing task. + +---- +package org.springframework.cloud.function.context +. . . +public interface PostProcessingFunction extends Function { + default void postProcess(Message result) { + } +} +---- + +So, now you have two options. + +Option 1: You can implement your function as `PostProcessingFunction` and also include the additional post processing behavior by implementing its `postProcess(Message>)` method. + +---- +private static class Uppercase implements PostProcessingFunction { + + @Override + public String apply(String input) { + return input.toUpperCase(); + } + + @Override + public void postProcess(Message result) { + System.out.println("Function Uppercase has been successfully invoked and its result successfully sent to target destination"); + } +} +. . . +@Bean +public Function uppercase() { + return new Uppercase(); +} +---- + +Option 2: If you already have an existing function and don't want to change its implementation or want to keep your function as POJO, you can simply implement only `postProcess(Message>)` method and compose this new post processing function with your other function. +---- +private static class Logger implements PostProcessingFunction { + + @Override + public void postProcess(Message result) { + System.out.println("Function has been successfully invoked and its result successfully sent to target destination"); + } +} +. . . +@Bean +public Function uppercase() { + return v -> v.toUpperCase(); +} +@Bean +public Function logger() { + return new Logger(); +} +. . . +// and then have your function definition as such `uppercase|logger` +---- + +NOTE: +In case of function composition only the last instance of `PostProcessingFunction` (if present) will take effect. For example, let's say you have the +following function definition - `foo|bar|baz` and both `foo` and `baz` are instances of `PostProcessingFunction`. Only `baz.postProcess(Message>)` will be invoked. +If `baz` is not an instance of `PostProcessingFunction`, then no post processing functionality will be performed. + +One may argue that you can easily do that via function composition by simply composing a post-processor as just another `Function`. That is indeed a possibility however +the post processing functionality in this case will be invoked right after invocation of the previous function and before the message is sent to a target destination +which is before the function invocation cycle is complete. [[spring-cloud-stream-overview-error-handling]] === Error Handling