Skip to content

Commit 8a8ef5a

Browse files
AdrienAdrien Poupard
Adrien
authored and
Adrien Poupard
committed
Docs: Update programming model with enhanced Kotlin support details
Signed-off-by: Adrien Poupard <[email protected]>
1 parent e23a63e commit 8a8ef5a

File tree

1 file changed

+158
-12
lines changed

1 file changed

+158
-12
lines changed

docs/modules/ROOT/pages/spring-cloud-function/programming-model.adoc

+158-12
Original file line numberDiff line numberDiff line change
@@ -705,31 +705,177 @@ However, given that `org.springframework.cloud.function.json.JsonMapper` is also
705705
[[kotlin-lambda-support]]
706706
== Kotlin Lambda support
707707

708-
We also provide support for Kotlin lambdas (since v2.0).
709-
Consider the following:
708+
Spring Cloud Function provides first-class support for Kotlin, allowing developers to leverage idiomatic Kotlin features, including coroutines and Flow, alongside imperative and Reactor-based programming models.
710709

711-
[source, java]
710+
=== Defining Functions in Kotlin
711+
712+
You can define Suppliers, Functions, and Consumers in Kotlin and register them as Spring beans using several approaches:
713+
714+
* **Kotlin Lambdas:** Define functions directly as lambda expressions within `@Bean` definitions. This is concise for simple functions.
715+
[source, kotlin]
716+
----
717+
@Configuration
718+
class MyKotlinConfiguration {
719+
720+
@Bean
721+
fun kotlinSupplier(): () -> String = { "Hello from Kotlin Lambda" }
722+
723+
@Bean
724+
fun kotlinFunction(): (String) -> String = { it.uppercase() }
725+
726+
@Bean
727+
fun kotlinConsumer(): (String) -> Unit = { println("Consumed by Kotlin Lambda: $it") }
728+
}
729+
----
730+
731+
* **Kotlin Classes implementing Kotlin Functional Types:** Define a class that directly implements the desired Kotlin functional type (e.g., `(String) -> String`, `suspend () -> Flow<Int>`).
732+
[source, kotlin]
733+
----
734+
@Component
735+
class UppercaseFunction : (String) -> String {
736+
override fun invoke(p1: String): String = p1.uppercase()
737+
}
738+
739+
// Can also be registered via @Bean
740+
----
741+
742+
* **Kotlin Classes implementing `java.util.function` Interfaces:** Define a Kotlin class that implements the standard Java `Supplier`, `Function`, or `Consumer` interfaces.
743+
[source, kotlin]
744+
----
745+
@Component
746+
class ReverseFunction : Function<String, String> {
747+
override fun apply(t: String): String = t.reversed()
748+
}
749+
----
750+
751+
Regardless of the definition style, beans of these types are registered with the `FunctionCatalog`, benefiting from features like type conversion and composition.
752+
753+
=== Coroutine Support (`suspend` and `Flow`)
754+
755+
A key feature is the seamless integration with Kotlin Coroutines. You can use `suspend` functions and `kotlinx.coroutines.flow.Flow` directly in your function signatures. The framework automatically handles the coroutine context and reactive stream conversions.
756+
757+
* **`suspend` Functions:** Functions marked with `suspend` can perform non-blocking operations using coroutine delays or other suspending calls.
758+
[source, kotlin]
712759
----
713760
@Bean
714-
open fun kotlinSupplier(): () -> String {
715-
return { "Hello from Kotlin" }
761+
fun suspendingFunction(): suspend (String) -> Int = {
762+
delay(100) // Non-blocking delay
763+
it.length
716764
}
717765
718766
@Bean
719-
open fun kotlinFunction(): (String) -> String {
720-
return { it.toUpperCase() }
767+
fun suspendingSupplier(): suspend () -> String = {
768+
delay(50)
769+
"Data from suspend"
721770
}
722771
723772
@Bean
724-
open fun kotlinConsumer(): (String) -> Unit {
725-
return { println(it) }
773+
fun suspendingConsumer(): suspend (String) -> Unit = {
774+
delay(20)
775+
println("Suspend consumed: $it")
726776
}
777+
----
727778

779+
* **`Flow` Integration:** Kotlin `Flow` can be used for reactive stream processing, similar to Reactor's `Flux`.
780+
[source, kotlin]
728781
----
729-
The above represents Kotlin lambdas configured as Spring beans. The signature of each maps to a Java equivalent of `Supplier`, `Function` and `Consumer`, and thus supported/recognized signatures by the framework.
730-
While mechanics of Kotlin-to-Java mapping are outside of the scope of this documentation, it is important to understand that the same rules for signature transformation outlined in "Java 8 function support" section are applied here as well.
782+
@Bean
783+
fun flowFunction(): (Flow<String>) -> Flow<Int> = { flow ->
784+
flow.map { it.length } // Process the stream reactively
785+
}
786+
787+
@Bean
788+
fun flowSupplier(): () -> Flow<String> = {
789+
flow { // kotlinx.coroutines.flow.flow builder
790+
emit("a")
791+
delay(10)
792+
emit("b")
793+
}
794+
}
795+
796+
// Consumer example taking a Flow
797+
@Bean
798+
fun flowConsumer(): suspend (Flow<String>) -> Unit = { flow ->
799+
flow.collect { item -> // Collect must happen within a coroutine scope
800+
println("Flow consumed: $item")
801+
}
802+
}
803+
----
804+
805+
* **Combining `suspend` and `Flow`:** You can combine `suspend` and `Flow` for complex asynchronous and streaming logic.
806+
[source, kotlin]
807+
----
808+
@Bean
809+
fun suspendingFlowFunction(): suspend (Flow<String>) -> Flow<String> = { incoming ->
810+
flow {
811+
delay(50) // Initial suspend
812+
incoming.collect {
813+
emit(it.uppercase()) // Process and emit
814+
}
815+
}
816+
}
817+
818+
@Bean
819+
fun suspendingFlowSupplier(): suspend () -> Flow<Int> = {
820+
flow {
821+
repeat(3) {
822+
delay(100)
823+
emit(it)
824+
}
825+
}
826+
}
827+
----
828+
829+
=== Reactive Types (`Mono`/`Flux`)
830+
831+
Kotlin functions can seamlessly use Reactor's `Mono` and `Flux` types, just like Java functions.
832+
[source, kotlin]
833+
----
834+
@Bean
835+
fun reactorFunction(): (Flux<String>) -> Mono<Int> = { flux ->
836+
flux.map { it.length }.reduce(0) { acc, i -> acc + i }
837+
}
838+
839+
@Bean
840+
fun monoSupplier(): () -> Mono<String> = {
841+
Mono.just("Reactive Hello")
842+
}
843+
----
844+
845+
=== `Message<T>` Support
846+
847+
Kotlin functions can also operate directly on `org.springframework.messaging.Message<T>` to access headers, including combinations with `suspend` and `Flow`.
848+
[source, kotlin]
849+
----
850+
@Bean
851+
fun messageFunction(): (Message<String>) -> Message<Int> = { msg ->
852+
MessageBuilder.withPayload(msg.payload.length)
853+
.copyHeaders(msg.headers)
854+
.setHeader("processed", true)
855+
.build()
856+
}
857+
858+
@Bean
859+
fun suspendMessageFunction(): suspend (Message<String>) -> Message<String> = { msg ->
860+
delay(100)
861+
MessageBuilder.withPayload(msg.payload.reversed())
862+
.copyHeaders(msg.headers)
863+
.build()
864+
}
865+
866+
@Bean
867+
fun flowMessageFunction(): (Flow<Message<String>>) -> Flow<Message<Int>> = { flow ->
868+
flow.map { msg ->
869+
MessageBuilder.withPayload(msg.payload.hashCode())
870+
.copyHeaders(msg.headers)
871+
.build()
872+
}
873+
}
874+
----
875+
876+
=== Kotlin Sample Project
731877

732-
To enable Kotlin support all you need is to add Kotlin SDK libraries on the classpath which will trigger appropriate autoconfiguration and supporting classes.
878+
For a comprehensive set of runnable examples showcasing these Kotlin features, please refer to the `src/test/kotlin/org/springframework/cloud/function/kotlin/arity` directory within the Spring Cloud Function repository. These examples demonstrate a wide range of function signatures with different arities, including regular functions, suspend functions (coroutines), and various reactive types (Flow, Mono, Flux).
733879

734880
[[function-component-scan]]
735881
== Function Component Scan

0 commit comments

Comments
 (0)