Add documentation for polling suppliers

Addressed PR comments
Resolves #POLLER
This commit is contained in:
Oleg Zhurakousky
2019-09-26 15:37:17 -04:00
committed by Oleg Zhurakousky
parent 84f29c51a3
commit ab15814cb7
4 changed files with 132 additions and 4 deletions

View File

@@ -485,6 +485,102 @@ NOTE: We are using `--spring.cloud.function.definition` property to explicitly d
we want to be bound to binding destinations. For cases when you only have single such bean it is not required
but for all other cases it is.
===== Suppliers (Sources)
`Function` and `Consumer` are pretty straightforward when it comes to how their invocation is triggered. They are triggered based
on data (events) sent to the destination they are bound to. In other words, they are classic event-driven components.
However, `Supplier` is in its own category when it comes to triggering. Since it is, by definition, the source (the origin) of the data, it does not
subscribe to any in-bound destination and, therefore, has to be triggered by some other mechanism(s).
There is also a question of `Supplier` implementation, which could be _imperative_ or _reactive_ and which directly relates to the triggering of such suppliers.
Consider the following sample:
[source,java]
----
@SpringBootApplication
public static class SupplierConfiguration {
@Bean
public Supplier<String> stringSupplier() {
return () -> "Hello from Supplier";
}
}
----
The preceding `Supplier` bean produces a string whenever its `get()` method is invoked. However, who invokes this method and how often?
The framework provides a default polling mechanism (answering the question of "Who?") that will trigger the invocation of the supplier and by default it will do so
every second (answering the question of "How often?").
In other words, the above configuration produces a single message every second and each message is sent to an `output` destination that is exposed by the binder.
To learn how to customize the polling mechanism, see <<Polling Configuration Properties>> section.
Consider a different sample:
[source,java]
----
@SpringBootApplication
public static class SupplierConfiguration {
@Bean
public Supplier<Flux<String>> stringSupplier() {
return () -> Flux.from(emitter -> {
while (true) {
try {
emitter.onNext("Hello from Supplier");
Thread.sleep(1000);
} catch (Exception e) {
// ignore
}
}
});
}
}
----
The preceding `Supplier` bean adopts the reactive programming style. Typically, and unlike the imperative supplier,
it should be triggered only once, given that the invocation of its `get()` method produces (supplies) the continuous stream of messages and not an
individual message.
The framework recognizes the difference in the programming style and guarantees that such a supplier is triggered only once.
However, imagine the use case where you want to poll some data source and return a finite stream of data representing the result set.
The reactive programming style is a perfect mechanism for such a Supplier. However, given the finite nature of the produced stream,
such Supplier still needs to be invoked periodically.
Consider the following sample, which emulates such use case by producing a finite stream of data:
[source,java]
----
@SpringBootApplication
public static class SupplierConfiguration {
@PollableSupplier
public Supplier<Flux<String>> stringSupplier() {
return () -> Flux.just("hello", "bye");
}
}
----
The bean itself is annotated with `PollableSupplier` annotation (sub-set of `@Bean`), thus signaling to the framework that although the implementation
of such a supplier is reactive, it still needs to be polled.
====== Polling Configuration Properties
The following properties are exposed by `org.springframework.cloud.stream.config.DefaultPollerProperties` and are prefixed with
`spring.cloud.stream.poller`:
fixedDelay::
Fixed delay for default poller.
+
Default: 1000L.
maxMessagesPerPoll::
Maximum messages for each polling event of the default poller.
+
Default: 1L.
For example `--spring.cloud.stream.poller.fixed-delay=2000` sets the poller interval to poll every two seconds.
===== Content-based routing with functions
Routing with functions can be achieved by relying on `RoutingFunction` available in Spring Cloud Function 3.0. All you need to do is enable it via
`--spring.cloud.stream.function.routing.enabled=true` application property. Once enabled `RoutingFunction` will be bound to input destination

View File

@@ -115,7 +115,6 @@
<module>spring-cloud-stream-test-support</module>
<module>spring-cloud-stream-test-support-internal</module>
<module>spring-cloud-stream-integration-tests</module>
<!-- <module>spring-cloud-stream-reactive</module> -->
<module>docs</module>
</modules>
<build>

View File

@@ -22,9 +22,9 @@ import org.springframework.scheduling.support.PeriodicTrigger;
/**
* @author Dave Syer
*
* @author Oleg Zhurakousky
*/
@ConfigurationProperties("spring.integration.poller")
@ConfigurationProperties("spring.cloud.stream.poller")
public class DefaultPollerProperties {
/**

View File

@@ -39,9 +39,11 @@ import org.springframework.context.annotation.Bean;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.handler.LoggingHandler;
import org.springframework.integration.scheduling.PollerMetadata;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.scheduling.support.PeriodicTrigger;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
@@ -247,7 +249,8 @@ public class ImplicitFunctionBindingTests {
SingleFunctionConfiguration.class))
.web(WebApplicationType.NONE)
.run("--spring.jmx.enabled=false",
"--spring.cloud.stream.bindings.input.content-type=text/plain")) {
"--spring.cloud.stream.bindings.input.content-type=text/plain",
"--debug")) {
InputDestination inputDestination = context.getBean(InputDestination.class);
OutputDestination outputDestination = context
@@ -288,6 +291,26 @@ public class ImplicitFunctionBindingTests {
}
}
@Test
public void testSupplierWithCustomPoller() {
System.clearProperty("spring.cloud.stream.function.definition");
try (ConfigurableApplicationContext context = new SpringApplicationBuilder(
TestChannelBinderConfiguration.getCompleteConfiguration(
SupplierWithExplicitPollerConfiguration.class))
.web(WebApplicationType.NONE)
.run("--spring.jmx.enabled=false",
"--spring.cloud.stream.poller.fixed-delay=2000")) {
OutputDestination outputDestination = context.getBean(OutputDestination.class);
PollerMetadata pollerMetadata = context.getBean(PollerMetadata.class);
assertThat(((PeriodicTrigger) pollerMetadata.getTrigger()).getPeriod()).isEqualTo(2000);
Message<byte[]> outputMessage = outputDestination.receive(6000);
assertThat(outputMessage.getPayload()).isEqualTo("hello".getBytes());
}
}
@EnableAutoConfiguration
public static class NoEnableBindingConfiguration {
@@ -393,4 +416,14 @@ public class ImplicitFunctionBindingTests {
}
@EnableAutoConfiguration
public static class SupplierWithExplicitPollerConfiguration {
@Bean
public Supplier<String> supplier() {
return () -> "hello";
}
}
}