GH-8626: Provide cleaner transform() DSL (#8653)

* GH-8626: Provide cleaner `transform()` DSL

Fixes https://github.com/spring-projects/spring-integration/issues/8626

* Add missed `transform(String beanName, @Nullable String methodName)` API
* Introduce a `TransformerSpec` to expose a strict API to configure
transformer variants.
* Introduce `transformWith(Consumer<TransformerSpec>)` as a single point
of all possible transformer and its endpoint options
* Deprecate those `IntegrationFlowDefinition.transform()` variants
which are harder to configure as several lambda arguments

This change will make Kotlin & Groovy DSL more readable and straightforward

* * Use new `transformWith()` in tests where a deprecated API still used
* Add JavaDocs to `TransformerSpec`
* Fix generic types for `BaseIntegrationFlowDefinition.transformWith()` -
they make sense exactly on the `TransformerSpec.transformer()` only
* Apply `transformWith()` for Groovy DSL
* Introduce a new `ClassUtils.isLambda(Object candidate)`
and add a check for Groovy `Closure`
* Fix `GroovyIntegrationFlowDefinition.createConfigurerIfAny()` to propagate a `Consumer` argument
down to the `Closure`

* * Rename `TransformerSpec -> TransformerEndpointSpec` for better context
meaning of the class
* Introduce `KotlinTransformerEndpointSpec` as an extension of the `TransformerEndpointSpec`
to have an `inline fun <reified P> transformer(crossinline function: (P) -> Any)`
for Kotlin style
* Add `KotlinIntegrationFlowDefinition.transformWith(KotlinTransformerEndpointSpec)`
* Deprecate Kotlin methods which are covered by the mentioned `transformWith()`
* Fix tests to use new API
* Mentioned the change in the doc
This commit is contained in:
Artem Bilan
2023-06-26 12:01:13 -04:00
committed by GitHub
parent ceb3daec8e
commit 070c1c6e60
20 changed files with 593 additions and 61 deletions

View File

@@ -283,8 +283,10 @@ The following example demonstrates how to change the publishing thread from the
public IntegrationFlow reactiveEndpointFlow() {
return IntegrationFlow
.from("inputChannel")
.<String, Integer>transform(Integer::parseInt,
e -> e.reactive(flux -> flux.publishOn(Schedulers.parallel())))
.transformWith(t -> t
.<String, Integer>transformer(Integer::parseInt)
.reactive(flux -> flux.publishOn(Schedulers.parallel()))
)
.get();
}
----
@@ -304,9 +306,13 @@ Each of them has generic arguments, so it lets you configure an endpoint and eve
@Bean
public IntegrationFlow flow2() {
return IntegrationFlow.from(this.inputChannel)
.transform(new PayloadSerializingTransformer(),
c -> c.autoStartup(false).id("payloadSerializingTransformer"))
.transform((Integer p) -> p * 2, c -> c.advice(this.expressionAdvice()))
.transformWith(t -> t
.transformer(new PayloadSerializingTransformer())
.autoStartup(false)
.id("payloadSerializingTransformer"))
.transformWith(t -> t
.transformer((Integer p) -> p * 2)
.advice(expressionAdvice()))
.get();
}
----
@@ -361,6 +367,10 @@ Nevertheless, the DSL parser takes care of bean declarations for inline objects,
See https://docs.spring.io/spring-integration/api/org/springframework/integration/dsl/Transformers.html[Transformers] in the Javadoc for more information and supported factory methods.
Starting with version 6.2, a `transformWith(Consumer<TransformerEndpointSpec>)` variant has been introduced to have all the transformer and its endpoint options to be configured via single builder argument.
This style gives DSL more readability and increases developer experience while modifying code.
This also make Groovy and Kotlin DSLs more straightforward.
Also see <<java-dsl-class-cast>>.
[[java-dsl-inbound-adapters]]
@@ -1428,4 +1438,4 @@ IntegrationFlow otherFlow() {
The composition in the middle of the flow is simply achievable with an existing `gateway(IntegrationFlow)` EIP-method.
This way we can build flows with any complexity by composing them from simpler, reusable logical blocks.
For example, you may add a library of `IntegrationFlow` beans as a dependency and it is just enough to have their configuration classes imported to the final project and autowired for your `IntegrationFlow` definitions.
For example, you may add a library of `IntegrationFlow` beans as a dependency, and it is just enough to have their configuration classes imported to the final project and autowired for your `IntegrationFlow` definitions.

View File

@@ -39,7 +39,10 @@ flowLambda() {
messageGroupId 'delayGroup'
defaultDelay 100
}
transform String, { it.toUpperCase() }
transform {
transformer { it.toUpperCase() }
expectedType String
}
}
}
----
@@ -78,8 +81,14 @@ functionFlow() {
integrationFlow Function<byte[], String>,
{ beanName 'functionGateway' },
{
transform Transformers.objectToString(), { id 'objectToStringTransformer' }
transform String, { it.toUpperCase() }
transform {
transformer Transformers.objectToString()
id 'objectToStringTransformer'
}
transform {
transformer { it.toUpperCase() }
expectedType String
}
split Message<?>, { it.payload }
split Object, { it }, { id 'splitterEndpoint' }
resequence()

View File

@@ -32,7 +32,7 @@ fun flowLambda() =
wireTap {
handle { println(it.payload) }
}
transform<String, String> { it.toUpperCase() }
transform<String> { it.toUpperCase() }
}
----
====
@@ -67,7 +67,7 @@ For example:
@Bean
fun functionFlow() =
integrationFlow<Function<String, String>>({ beanName("functionGateway") }) {
transform<String, String> { it.toUpperCase() }
transform<String> { it.toUpperCase() }
}
@Bean

View File

@@ -28,3 +28,6 @@ See <<./debezium.adoc#debezium, Debezium Support>> for more information.
- The XML configuration for `<poller>` and `@Poller` annotation now support ISO 8601 duration format for `fixed-delay`, `fixed-rate` and `initial-delay` options.
See <<./endpoint.adoc#endpoint-pollingconsumer, Polling Consumer>> for more information.
- Java, Groovy and Kotlin DSLs have now context-specific methods in the `IntegationFlowDefinition` with a single `Consumer` argument to configure an endpoint and its handler with one builder and readable options.
See, for example, `transformWith()` in <<./dsl.adoc#java-dsl, Java DSL Chapter>>.