From 0d5b68a4b5d61a22dc4acf94c3e60ba5b53ea4ac Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 25 Mar 2021 15:31:31 +0100 Subject: [PATCH] Add documentation and test to demo header enrichment with function composition --- .../main/asciidoc/spring-cloud-stream.adoc | 55 ++++++++++++++++++- .../ImplicitFunctionBindingTests.java | 1 + 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/spring-cloud-stream.adoc b/docs/src/main/asciidoc/spring-cloud-stream.adoc index e74f8ced7..f137ea2b1 100644 --- a/docs/src/main/asciidoc/spring-cloud-stream.adoc +++ b/docs/src/main/asciidoc/spring-cloud-stream.adoc @@ -790,8 +790,59 @@ presenting a great deal of inconvenience when it comes to other configuration pr feature described in <> section can help. For example, if we want to give our `toUpperCase|wrapInQuotes` a more descriptive name we can do so -with the following property `spring.cloud.stream.function.bindings.toUpperCase|wrapInQuotes=quotedUpperCase` allowing -other configuration properties to refer to that binding name (e.g., `spring.cloud.stream.bindings.quotedUpperCase.destination=myDestination`). +with the following property `spring.cloud.stream.function.bindings.toUpperCase|wrapInQuotes-in-0=quotedUpperCaseInput` allowing +other configuration properties to refer to that binding name (e.g., `spring.cloud.stream.bindings.quotedUpperCaseInput.destination=myDestination`). + +====== Functional Composition and Cross-cutting Concerns + +Function composition effectively allows you to address complexity by breaking it down +to a set of simple and individually manageable/testable components that could still be +represented as one at runtime. But that is not the only benefit. + +You can also use composition to address certain cross-cutting non-functional concerns, +such as content enrichment. For example, assume you have an incoming message that may +be lacking certain headers, or some headers are not in the exact state your business +function would expect. You can now implement a separate function that addresses those +concerns and then compose it with the main business function. + +Let’s look at the example + +[source,java] +---- +@SpringBootApplication +public class DemoStreamApplication { + + public static void main(String[] args) { + SpringApplication.run(DemoStreamApplication.class, + "--spring.cloud.function.definition=enrich|echo", + "--spring.cloud.stream.function.bindings.enrich|echo-in-0=input", + "--spring.cloud.stream.bindings.input.destination=myDestination", + "--spring.cloud.stream.bindings.input.group=myGroup"); + + } + + @Bean + public Function, Message> enrich() { + return message -> { + Assert.isTrue(!message.getHeaders().containsKey("foo"), "Should NOT contain 'foo' header"); + return MessageBuilder.fromMessage(message).setHeader("foo", "bar").build(); + }; + } + + @Bean + public Function, Message> echo() { + return message -> { + Assert.isTrue(message.getHeaders().containsKey("foo"), "Should contain 'foo' header"); + System.out.println("Incoming message " + message); + return message; + }; + } +} +---- + +While trivial, this example demonstrates how one function enriches the incoming Message with the additional header(s) (non-functional concern), +so the other function - `echo` - can benefit form it. The `echo` function stays clean and focused on business logic only. +You can also see the usage of `spring.cloud.stream.function.bindings` property to simplify composed binding name. ===== Functions with multiple input and output arguments diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/ImplicitFunctionBindingTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/ImplicitFunctionBindingTests.java index 4a22519a0..38e71d9e8 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/ImplicitFunctionBindingTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/ImplicitFunctionBindingTests.java @@ -40,6 +40,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.function.context.FunctionRegistration; import org.springframework.cloud.function.context.FunctionType; +import org.springframework.cloud.function.context.catalog.FunctionAroundWrapper; import org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.annotation.StreamListener;