Puente entre Corrutinas y Reactor
Habla ambos dialectos con fluidez: espera un Mono/Flux desde codigo suspend y envuelve codigo suspend en un Publisher.
Spring WebFlux y el ecosistema reactivo en general se construyen sobre los dos tipos centrales de Project Reactor: un `Mono<T>` que emite cero o un elemento, y un `Flux<T>` que emite de cero a muchos. Las corrutinas de Kotlin hablan un dialecto totalmente distinto: funciones `suspend` que se leen como codigo secuencial, y `Flow<T>` para flujos asincronos. Los modulos `kotlinx-coroutines-reactor` (y `kotlinx-coroutines-reactive`) son el puente oficial y bidireccional entre ambos mundos, permitiendote consumir un `Publisher` reactivo dentro de una corrutina y, a la inversa, exponer codigo de corrutinas como un `Publisher` al que Reactor puede suscribirse.
Ir de Reactor hacia corrutinas es la direccion mas comun, porque muchas APIs de Spring (WebClient, los repositorios reactivos, R2DBC) te entregan un `Mono` o un `Flux`. El puente ofrece operadores `await` suspendibles que se suscriben por debajo y reanudan tu corrutina con el resultado. Para un `Mono`, usa `awaitSingle()` cuando esperas exactamente un valor, `awaitSingleOrNull()` cuando el `Mono` puede estar vacio, y los operadores de un solo elemento `awaitFirst()` / `awaitFirstOrNull()` / `awaitLast()` cuando trabajas con un `Flux` y solo te importa un elemento. Estas funciones son fieles a los contratos de Reactor: `awaitSingle()` lanza `NoSuchElementException` ante una fuente vacia e `IllegalArgumentException` si llega mas de un elemento, asi que elige el operador acorde a la cardinalidad que realmente esperas.
Una sutileza crucial es el `Mono` vacio. Reactor modela 'sin valor' como una senal de completado sin emision, no como `null`, de modo que llamar a `awaitSingle()` sobre un `Mono` vacio lanza `NoSuchElementException` en lugar de devolver `null`. Cuando un valor es genuinamente opcional — una busqueda que puede fallar, un `deleteById` que devuelve `Mono<Void>` — recurre a `awaitSingleOrNull()` (o `awaitFirstOrNull()` en un `Flux`). Esto mapea el completado vacio de Reactor a los tipos nullables de Kotlin de forma limpia, para que tus `?:`, `?.let` y smart-casts se comporten exactamente como espera un desarrollador de Kotlin.
El `WebClient` reactivo de Spring agrega su propia comodidad encima de esto: `awaitBody<T>()` y `awaitBodyOrNull<T>()`. Son funciones de extension que internamente llaman a `bodyToMono<T>()` y luego lo esperan, colapsando la cadena habitual `.retrieve().bodyToMono(User::class.java).awaitSingle()` en una sola llamada con tipo reificado. Como el tipo es reificado escribes `awaitBody<User>()` sin argumento `.class`, y la informacion generica sobrevive al borrado de tipos para la deserializacion. Para respuestas en streaming puedes en cambio llamar a `bodyToFlow<T>()` y obtener un `Flow` de Kotlin para recolectarlo idiomaticamente.
La direccion inversa — exponer codigo suspend como un `Publisher` — la resuelven los constructores de corrutinas `mono { }` y `flux { }`. `mono { block }` ejecuta tu bloque suspendible y produce un `Mono<T>` que emite el valor de retorno del bloque (o completa vacio si el bloque devuelve `null`); `flux { block }` ejecuta el bloque con un receptor `ProducerScope<T>` cuya API estilo canal `send()` te permite emitir muchos valores desde codigo suspend. Estas son las herramientas que necesitas cuando un framework exige un tipo de retorno reactivo pero quieres escribir la logica con corrutinas — por ejemplo un `ReactiveAuthenticationManager` de Spring Security, un `WebFilter`, o cualquier contrato tipado como `Mono`/`Flux` que no puedas cambiar a `suspend`.
Ambos constructores aceptan un argumento opcional `CoroutineContext` (que por defecto es `EmptyCoroutineContext`, lo que significa que se usa el contexto del suscriptor). De forma critica, `mono { }` y `flux { }` son frios y perezosos: el bloque no se ejecuta hasta que algo se suscribe, y vuelve a ejecutarse en cada suscripcion: coincide con la semantica de publisher frio de Reactor en lugar de lanzarse de inmediato como `launch` o `async`. La cancelacion tambien queda conectada: si el suscriptor de abajo cancela, la corrutina se cancela y tus llamadas `suspend` lo observan cooperativamente, asi obtienes una limpieza de recursos correcta de forma gratuita.
En un controlador tipico de Spring Boot 3 rara vez necesitas estos constructores, porque Spring WebFlux entiende funciones `suspend` y `Flow<T>` de forma nativa: un manejador `suspend fun` que devuelve `User` se adapta automaticamente a `Mono<User>`, y un `Flow<User>` a `Flux<User>`. El puente demuestra su valor en las fronteras: dentro de un manejador suspend llamas a APIs reactivas de terceros y haces `awaitSingle()` de sus resultados, y solo recurres a `mono { }` / `flux { }` cuando una firma de interfaz fuera de tu control esta tipada rigidamente a un `Publisher`.
Algunas guias practicas lo amarran todo. Prefiere `Flow` sobre `Flux` para la logica de flujo que controlas, convirtiendo en los bordes con `.asFlow()` y `.asFlux()`. Elige la variante `*OrNull` en cuanto la ausencia de valor sea un resultado legal, y deja que la variante que lanza excepciones proteja invariantes genuinos. Agrega la dependencia `org.jetbrains.kotlinx:kotlinx-coroutines-reactor` (que trae transitivamente `-reactive`) para tener tanto los operadores await como los constructores en tu classpath. Bien hecho, el puente deja la fontaneria reactiva invisible mientras escribes Kotlin lineal, depurable y de apariencia secuencial.
import kotlinx.coroutines.reactor.awaitSingleimport kotlinx.coroutines.reactor.awaitSingleOrNullimport reactor.core.publisher.Mono// Imagine these come from a reactive repository / WebClientfun findUser(id: Long): Mono<User> = Mono.just(User(id, "Ada"))fun findMissing(): Mono<User> = Mono.empty()suspend fun loadUsers() {// Exactly one expected -> awaitSingle()val user: User = findUser(1).awaitSingle()println("Loaded " + user.name)// May be empty -> awaitSingleOrNull() returns null instead of throwingval maybe: User? = findMissing().awaitSingleOrNull()println(maybe?.name ?: "no user found")}data class User(val id: Long, val name: String)
Consuming Reactor types from suspend code. awaitSingle expects exactly one value; awaitSingleOrNull maps an empty Mono to a Kotlin null instead of throwing.
import org.springframework.web.reactive.function.client.WebClientimport org.springframework.web.reactive.function.client.awaitBodyimport org.springframework.web.bind.annotation.*@RestControllerclass ProfileController(private val client: WebClient) {@GetMapping("/profile/{id}")suspend fun profile(@PathVariable id: Long): Profile {// reified body type, no .class argument, internally awaits the Monoreturn client.get().uri("/users/" + id).retrieve().awaitBody<Profile>()}}data class Profile(val id: Long, val handle: String)
A WebFlux suspend controller: awaitBody<T>() collapses retrieve().bodyToMono(T).awaitSingle() into one reified call. Spring adapts the suspend return to Mono automatically.
import kotlinx.coroutines.reactor.monoimport kotlinx.coroutines.delayimport reactor.core.publisher.Mono// Pretend this signature is dictated by a framework interface you cannot change.fun authenticate(token: String): Mono<Principal> = mono {// suspend code lives here; it runs only when subscribeddelay(50) // e.g. a suspend call to a token serviceif (token == "valid") Principal("ada") else null // null -> empty Mono}data class Principal(val name: String)
The reverse bridge: expose suspend logic as a Publisher when an interface is hard-typed to Mono. mono { } is cold and re-runs on each subscription; returning null completes it empty (Reactor cannot carry a null value).
import kotlinx.coroutines.runBlockingimport kotlinx.coroutines.flow.flowimport kotlinx.coroutines.flow.firstimport kotlinx.coroutines.flow.toListimport kotlinx.coroutines.flow.mapfun main() = runBlocking {// A Flow models the same stream concept Flux does, but with suspend operators.val numbers = flow {for (n in 1..3) emit(n)}val first = numbers.first() // analogous to Flux.awaitFirst()val all = numbers.map { it * 10 }.toList()println("first = " + first) // first = 1println("all = " + all) // all = [10, 20, 30]}
Pure stdlib + kotlinx.coroutines: asFlow()/asFlux() convert at the edges and Flow.first() mirrors awaitFirst(). No Spring needed, so this actually runs.
Arena IDE🧠Comprueba tu comprensión
0/1 · 0/1 answered1. You call a reactive repository method that returns a Mono<User> which completes empty when no row matches. Inside a suspend function you want a User? back (null when absent) without throwing. Which operator should you use?