Could someone help me to understand the difference between:
Mono.defer()
Mono.create()
Mono.just()
How to use it properly?
Could someone help me to understand the difference between:
Mono.defer()
Mono.create()
Mono.just()
How to use it properly?
Mono.just(value)
is the most primitive - once you have a value you can wrap it into a Mono and subscribers down the line will get it.
Mono.defer(monoSupplier)
lets you provide the whole expression that supplies the resulting Mono
instance. The evaluation of this expression is deferred until somebody subscribes. Inside of this expression you can additionally use control structures like Mono.error(throwable)
to signal an error condition (you cannot do this with Mono.just
).
Mono.create(monoSinkConsumer)
is the most advanced method that gives you the full control over the emitted values. Instead of the need to return Mono
instance from the callback (as in Mono.defer
), you get control over the MonoSink<T>
that lets you emit values through MonoSink.success()
, MonoSink.success(value)
, MonoSink.error(throwable)
methods.
Reactor documentation contains a few good examples of possible Mono.create
use cases: link to doc.
The general advice is to use the least powerful abstraction to do the job: Mono.just -> Mono.defer -> Mono.create
.
Although in general I agree with (and praise) @IlyaZinkovich's answer, I would be careful with the advice
The general advice is to use the least powerful abstraction to do the job:
Mono.just
->Mono.defer
->Mono.create
.
In the reactive approach, especially if we are beginners, it's very easy to overlook which the "least powerful abstraction" actually is. I am not saying anything else than @IlyaZinkovich, just depicting one detailed aspect.
Here is one specific use case where the more powerful abstraction Mono.defer()
is preferable over Mono.just()
but which might not be visible at the first glance.
See also:
We use switchIfEmpty()
as a subscription-time branching:
// First ask provider1
provider1.provide1(someData)
// If provider1 did not provide the result, ask the fallback provider provider2
.switchIfEmpty(provider2.provide2(someData))
public Mono<MyResponse> provide2(MyRequest someData) {
// The Mono assembly is needed only in some corner cases
// but in fact it is always happening
return Mono.just(someData)
// expensive data processing which might even fail in the assemble time
.map(...)
.map(...)
...
}
provider2.provide2()
accepts someData
only when provider1.provide1()
does not return any result, and/or the method assembly of the Mono
returned by provider2.provide2()
is expensive and even fails when called on wrong data.
It this case defer()
is preferable, even if it might not be obvious at the first glance:
provider1.provide1(someData)
// ONLY IF provider1 did not provide the result, assemble another Mono with provider2.provide()
.switchIfEmpty(Mono.defer(() -> provider2.provide2(someData)))
public Mono<DrawDetail> getDrawDetail(int id, int year) {
return drawDetailCacheService.getDrawDetail(year, id)
.doOnNext(detail -> LOGGER.debug("draw detail {}-{} found in cache", year, id))
.switchIfEmpty(
Mono.defer(() -> {
LOGGER.debug("draw detail {}-{} not found in cache calling remote...", year, id);
return Mono.just(callRemoteEventDetailAndSendToCache(id, year));
})
);
}
Here's what is difference between Mono.just() and Mono.defer() :
When you use Mono.just(...), the argument inside just is evaluated immediately. This means if you had written:
.switchIfEmpty(Mono.just(callRemoteEventDetailAndSendToCache(id, year)))
The callRemoteEventDetailAndSendToCache(id, year) method would be executed immediately, regardless of whether the cache service returns a value or not. This is definitely not what you'd want, especially if the method has side effects like making remote calls or modifying state.
In contrast, when you use Mono.defer(...), the provided lambda is only executed when the Mono is subscribed to. In the context of switchIfEmpty(), this means the lambda will only execute if the preceding Mono (from the cache service) is empty.
By using Mono.defer(), the callRemoteEventDetailAndSendToCache(id, year) method gets lazily invoked only when needed (i.e., when the cache doesn't have the DrawDetail)
© 2022 - 2025 — McMap. All rights reserved.